'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 1:[1]
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 an architectural 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.)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |