You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Security报错:必须设置UserDetailsService问题求助

问题分析与解决

问题原因

你的CustomAuthProvider标注了@Service注解,Spring容器启动时会自动扫描并实例化这个Bean。而它的父类DaoAuthenticationProvider在初始化阶段会执行afterPropertiesSet()方法,强制检查UserDetailsService是否已设置,但Spring自动实例化的CustomAuthProvider实例并没有被注入UserDetailsService——只有你在SecurityConfig的authProvider()方法里手动创建的实例才配置了该服务,这直接导致了初始化时的IllegalArgumentException。

同时,这种既通过@Service自动实例化、又手动创建Bean的方式,还造成了Bean的重复定义,进一步加剧了问题。

解决方法

  1. 移除CustomAuthProvider上的@Service注解,禁止Spring自动实例化它,只保留SecurityConfig中通过@Bean定义的唯一实例。
  2. 改用构造注入替代字段注入,明确依赖关系,避免注入时机问题。
  3. 优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 10:30:43