Spring Boot Security报错:必须设置UserDetailsService问题求助
问题分析与解决
问题原因
你的CustomAuthProvider标注了@Service注解,Spring容器启动时会自动扫描并实例化这个Bean。而它的父类DaoAuthenticationProvider在初始化阶段会执行afterPropertiesSet()方法,强制检查UserDetailsService是否已设置,但Spring自动实例化的CustomAuthProvider实例并没有被注入UserDetailsService——只有你在SecurityConfig的authProvider()方法里手动创建的实例才配置了该服务,这直接导致了初始化时的IllegalArgumentException。
同时,这种既通过@Service自动实例化、又手动创建Bean的方式,还造成了Bean的重复定义,进一步加剧了问题。
解决方法
- 移除
CustomAuthProvider上的@Service注解,禁止Spring自动实例化它,只保留SecurityConfig中通过@Bean定义的唯一实例。 - 改用构造注入替代字段注入,明确依赖关系,避免注入时机问题。
- 优化
SecurityFilterChain配置,移除手动创建ProviderManager的代码,让Spring自动关联自定义的认证提供者。
修正后的代码
1. 自定义认证提供者(移除@Service)
public class CustomAuthProvider extends DaoAuthenticationProvider { Logger logger = LoggerFactory.getLogger(getClass()); private final AuthUserRepo authUserRepo; // 构造注入AuthUserRepo public CustomAuthProvider(AuthUserRepo authUserRepo) { this.authUserRepo = authUserRepo; } @Override public Authentication authenticate(final Authentication authentication) throws AuthenticationException { logger.info("CustomAuthProvider authentication ::::: {} ", authentication); logger.info("CustomAuthProvider authentication.getPrincipal() ::::: {} ", authentication.getPrincipal().toString()); final String name = authentication.getName(); final String password = authentication.getCredentials().toString(); logger.info("CustomAuthenticationProvider ::::: password: {}, username: {}", password, name); if (name == null || password == null) { throw new BadCredentialsException("Invalid username or password"); } Optional<AuthUser> authUser = authUserRepo.findByEmail(name); if (authUser.isEmpty()) { throw new BadCredentialsException("Invalid username or password"); } final Authentication result = super.authenticate(authentication); return new UsernamePasswordAuthenticationToken(authUser.get(), result.getCredentials().toString(), result.getAuthorities()); } }
2. 安全配置类(优化Bean定义与注入)
@Configuration @EnableWebSecurity public class SecurityConfig { Logger logger = LoggerFactory.getLogger(getClass()); private final CustomUserDetailService userDetailsService; private final AuthUserRepo authUserRepo; private final PasswordEncoder passwordEncoder; // 构造注入所有依赖 public SecurityConfig(CustomUserDetailService userDetailsService, AuthUserRepo authUserRepo, PasswordEncoder passwordEncoder) { this.userDetailsService = userDetailsService; this.authUserRepo = authUserRepo; this.passwordEncoder = passwordEncoder; } @Bean public DaoAuthenticationProvider authProvider() { CustomAuthProvider authProvider = new CustomAuthProvider(authUserRepo); authProvider.setUserDetailsService(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder); return authProvider; } @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { return http.authorizeHttpRequests(auth -> auth .mvcMatchers("/favicon.ico", "/signup", "/cdn.jsdelivr.net/**").permitAll() .anyRequest().authenticated()) .formLogin(form -> form.loginPage("/login").permitAll()) // 关联自定义认证提供者 .authenticationProvider(authProvider()) .csrf(csrf -> csrf.disable()) .build(); } @Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
3. 自定义用户详情服务(改用构造注入)
@Service public class CustomUserDetailService implements UserDetailsService { Logger logger = LoggerFactory.getLogger(getClass()); private final AuthUserRepo authUserRepo; // 构造注入AuthUserRepo public CustomUserDetailService(AuthUserRepo authUserRepo) { this.authUserRepo = authUserRepo; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { logger.info("CustomUserDetailService -> UserName :::: {}", username); Optional<AuthUser> savedUser = authUserRepo.findByEmail(username); if (savedUser.isEmpty()) { throw new UsernameNotFoundException("No user found with username: " + username); } return new org.springframework.security.core.userdetails.User( savedUser.get().getEmail(), savedUser.get().getPassword(), true, true, true, true, getAuthorities()); } private Collection<? extends GrantedAuthority> getAuthorities() { final List<GrantedAuthority> authorities = new ArrayList<>(); List<String> roles = Arrays.asList("Associate", "Manager"); for (String role : roles) { authorities.add(new SimpleGrantedAuthority(role)); } return authorities; } }
额外提示
- 构造注入是Spring官方推荐的依赖注入方式,能明确依赖关系,提升代码可测试性。
NoOpPasswordEncoder仅适用于测试环境,生产环境必须使用BCryptPasswordEncoder等安全的密码编码器。- 无需手动创建
ProviderManager,Spring会自动整合所有注册的AuthenticationProvider实例。
内容的提问来源于stack exchange,提问作者Feroz Siddiqui
相关产品推荐
相关产品推荐

