Spring Security报错:A UserDetailsService must be set 求助
Spring Security Bean创建异常排查:UserDetailsService must be set
问题背景
尝试运行他人的Maven项目,升级至2.5.0版本后因WebSecurityConfigurerAdapter相关问题降级,当前登录功能失效,抛出Bean创建异常:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'authenticationProvider' defined in class path resource com/shop/WebSecurityConfig.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: A UserDetailsService must be set Caused by: java.lang.IllegalArgumentException: A UserDetailsService must be set at org.springframework.util.Assert.notNull(Assert.java:201) ~[spring-core-5.3.7.jar!/:5.3.7] at org.springframework.security.authentication.dao.DaoAuthenticationProvider.doAfterPropertiesSet(DaoAuthenticationProvider.java:85) ~[spring-security-core-5.5.0.jar!/:5.5.0]
相关代码
WebSecurityConfig.java
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { private MyUserDetailsService myUserDetailsService; @Autowired public void setMyUserDetailsService(MyUserDetailsService myUserDetailsService) { this.myUserDetailsService = myUserDetailsService; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/product/**","/","/home**","/user/**","/registration**","/products**","/default**").permitAll() .antMatchers("/resources/**", "/static/**","/bower_components/**","/js/**","/css/**","/images/**").permitAll() .antMatchers("/cabinet**","/principle/**" ).authenticated() .antMatchers("/**").hasAuthority(String.valueOf(Role.ADMIN)) .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/default") .successForwardUrl("/default") .permitAll() .and() .logout() .permitAll().deleteCookies("JSESSIONID").and() .exceptionHandling().accessDeniedPage("/accessDenied"); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(authenticationProvider()) .inMemoryAuthentication() .withUser("user").password("pass").roles(String.valueOf(Role.USER)).and() .withUser("admin").password("pass").roles(String.valueOf(Role.ADMIN)); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(myUserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
MyUserDetailsService.java
@Service public class MyUserDetailsService implements UserDetailsService { private static Logger LOG = LoggerFactory.getLogger(MyUserDetailsService.class); private UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByEmail(username); if (user == null) { throw new UsernameNotFoundException(username); } LOG.info("Finded user : [" + username + "]"); return new MyUserPrincipal(user); } public class MyUserPrincipal extends User implements UserDetails { private User user; private MyUserPrincipal(User user) { this.user = user; } public User getUser() { return user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return AuthorityUtils.createAuthorityList(user.getRole().toString()); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return user.getIsAccountNonLocked(); } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
排查与解决思路
1. 依赖注入时机问题(核心原因)
authenticationProvider Bean创建时,类中的myUserDetailsService还未通过setter完成注入,导致为null。Spring中@Bean方法的执行可能早于@Autowired setter的触发。
解决方式二选一:
改用构造方法注入:
private final MyUserDetailsService myUserDetailsService; // Spring 4.3+无需@Autowired,构造方法自动注入 public WebSecurityConfig(MyUserDetailsService myUserDetailsService) { this.myUserDetailsService = myUserDetailsService; }删除原有的@Autowired setter方法。
直接在@Bean方法中通过参数注入:
@Bean public DaoAuthenticationProvider authenticationProvider(MyUserDetailsService myUserDetailsService) { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(myUserDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; }同时删除类中定义的
myUserDetailsService字段和对应的setter。
2. 认证逻辑冲突问题
configureGlobal中同时配置了authenticationProvider(数据库认证)和inMemoryAuthentication(内存用户),两者会共存但可能导致认证优先级混乱。如果需要使用数据库用户认证,建议移除内存用户配置;若要保留内存用户,需确保密码编码一致。
3. 密码编码一致性问题
内存用户的密码是明文"pass",但DaoAuthenticationProvider使用BCryptPasswordEncoder,内存用户登录会因密码不匹配失败。若保留内存用户,需修改为编码后的密码:
.withUser("user").password(passwordEncoder().encode("pass")).roles(String.valueOf(Role.USER))
4. MyUserPrincipal类设计优化
MyUserPrincipal继承了Spring Security的User类,同时持有自定义User对象,容易导致字段/方法混淆。建议改为仅实现UserDetails接口:
public class MyUserPrincipal implements UserDetails { private final User user; public MyUserPrincipal(User user) { this.user = user; } // 实现UserDetails所有方法,基于user对象返回对应属性 @Override public Collection<? extends GrantedAuthority> getAuthorities() { return AuthorityUtils.createAuthorityList(user.getRole().toString()); } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return user.getIsAccountNonLocked(); } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
内容的提问来源于stack exchange,提问作者Knut
相关产品推荐
相关产品推荐

