Search code examples
javaspringaop

Spring error "Bean named 'x' is expected to be of type 'y', but was actually of type [com.sun.proxy.$Proxy]"


I am trying to implement a DAO based authentication in an application using Spring Security.

When I tried to log in to the application with a user I got this error:

failed to lazily initialize a collection of role: com.example.app.dao.User.groups, could not initialize proxy - no Session

Looking at @jcmwright80 's answer to this question I understood that I should ideally annotate UserDetailsServiceImpl class as @Transactional. After doing that I got an error during login:

Bean named 'userDetailsService' is expected to be of type 'com.example.app.service.UserDetailsServiceImpl' but was actually of type 'com.sun.proxy.$Proxy238'"}}

This seems to be an issue related to the proxy object created on UserDetailsServiceImpl - how can I fix this gracefully?

CODE

Relevant part of the security configuration:

@Configuration
@ComponentScan("com.example.app.service")
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter  {

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

}

UserDetailsServiceImpl.java

@Service
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService{

    public UserDetailsServiceImpl () {};
    
    @Autowired
    private UserDao userDao;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        
        User user = userDao.getUser(username);
        
        if (user == null) {
            throw new UsernameNotFoundException ("User not found.");
        }
        return new UserDetailsImpl(user);
    }
  }

User.java

@Entity
@Table(name="users",schema="sec")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="userGen")
    @SequenceGenerator(name="userGen", sequenceName="user_id_seq", schema="sec")
    private long id;    

    // Validation constraints on the fields ...
    private String username;
    private String password;
    private boolean enabled;
    
    @ManyToMany
    @JoinTable(name="group_members", schema="sec", joinColumns= { @JoinColumn(name="user_id") }, inverseJoinColumns = { @JoinColumn(name="group_id") } )
    private Set<Group> groups;

 // Getters, Setters etc. ...
 }

(The alternative solution of using @ManyToMany(fetch = FetchType.EAGER) on collection type fields in User and Group classes works, though it could impact performance.)


Solution

  • Solution 1 (Optimal)

    The methods used in the @Service class should all be declared in an interface that the @Service implements. Then the service can be referred to via its interface type everywhere (e.g. in @Controller class). This way Spring can continue to use JDK dynamic proxies (which are preferred over CGLIB).

    This was especially important as an interface provided by Spring was implemented: org.springframework.security.core.userdetails.UserDetailsService which declared only 1 method but others were required too.
    This was rather a design problem than a simple coding issue.

    Step by step:

    public interface CustomizedUserDetailsService extends UserDetailsService {
        void add(User user);
    }
    

    Implement the methods in the service:

    @Service
    public class UserDetailsServiceImpl implements CustomizedUserDetailsService{
        @Override
        @Transactional
        public void add (User user) {
            //...
        }
    }
    

    Reference the bean through its interface type in the @Configuration but return the implementation:

    @Bean
    @Primary
    public CustomizedUserDetailsService userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    

    While using the implementation inject the bean through a reference to its implemented interface:

    @Controller
    public class UserController {
    
        private CustomizedUserDetailsService userDetailsService;
        
        @Autowired
        public void setUsersService(CustomizedUserDetailsService customizedUserDetailsService) {
            this.userDetailsService = customizedUserDetailsService;
        }
    }
    

    More details on proxies here.

    Solution 2

    Use an annotation to enable CGLIB proxy on the service class. This required spring-aop and aspectjweaver dependencies (not using Spring Boot).

    @EnableAspectJAutoProxy(proxyTargetClass=true)
    

    Then the security configuration class can produce the bean like this:

    @Bean
    public UserDetailsServiceImpl userDetailsService() {
        return new UserDetailsServiceImpl();
    }
    

    (The DAO layer should not be @Transactional, that should only be applied to the @Service class or specific methods of the latter class.)