【发布时间】:2021-03-21 22:27:11
【问题描述】:
我正在尝试使用 Spring Security 在应用程序中实现基于 DAO 的身份验证。
当我尝试使用用户登录应用程序时,出现此错误:
failed to lazily initialize a collection of role: com.intellivest.app.dao.User.groups, could not initialize proxy - no Session
看着@jcmwright80 对this question 的回答,我明白我最好将UserDetailsServiceImpl 类注释为@Transactional。之后我在登录时遇到了错误:
Bean named 'userDetailsService' is expected to be of type 'com.intellivest.app.service.UserDetailsServiceImpl' but was actually of type 'com.sun.proxy.$Proxy238'"}}
这似乎是与在 UserDetailsServiceImpl 上创建的代理对象有关的问题 - 我该如何优雅地解决这个问题?
代码
安全配置的相关部分:
@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);
}
}
用户.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. ...
}
(在User 和Group 类中的集合类型字段上使用@ManyToMany(fetch = FetchType.EAGER) 的替代解决方案有效,尽管它可能会影响性能。)
【问题讨论】:
-
我认为你的代码有问题:你有
@Bean UserDetailsService userDetailsService()vs.@Service("userDetailsService") class UserDetailsServiceImpl。看起来有两种名称相同的方法可以用两种不同的类型实例化服务 bean。@Bean具有接口类型,这是作为 JDK 代理创建的。在另一个地方,您明确声明@Service具有实现类型,这与 bean 工厂方法相矛盾。我认为您需要修复这种不一致,因为它会导致您的问题,如错误消息所示。 -
@kriegaex 实际上这可能与问题有关。当我在
SecurityConfig中将@Bean的返回类型更改为UserDetailsServiceImpl时,它对错误给出了更清晰的描述:java.lang.IllegalStateException: @Bean method SecurityConfig.userDetailsService called as bean reference for type [com.intellivest.app.service.UserDetailsServiceImpl] but overridden by non-compatible bean instance of type [com.sun.proxy.$Proxy330].Spring 建议>“只要你有选择,JDK 动态代理是首选”什么解决方案你建议记住这一点吗? -
我不是 Spring 用户。但基本上,在使用 JDK 代理时,您必须确保所有内容都被引用并实例化为接口类型,从类名
com.sun.proxy.$Proxy330可以看出,这是在您的应用程序某处创建的类型。也许将@EnableAspectJAutoProxy添加到您的配置类就足够了。或者,您可以通过@EnableAspectJAutoProxy(proxyTargetClass=true)强制使用 CGLIB 代理。我太忙了,没有时间把你所有的类都复制到一个新项目中去玩,如果你有一个 GitHub 项目我可以看看。