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

新增Employer AuthenticationProvider后调用authenticate出现StackOverflowError

问题分析与解决方案:多认证Provider导致StackOverflowError

问题描述

项目包含普通用户和雇主两条认证路径,最初仅实现普通用户的认证过滤器与AuthenticationProvider时功能正常。新增雇主AuthenticationProvider后,调用AuthenticationManager.authenticate(userPassAuthToken)时触发java.lang.StackOverflowError: null异常,调试时卡在过滤器的该调用行,且程序不会进入用户认证Provider的逻辑。

核心问题定位

  1. 用户认证Provider逻辑错误:UserPassAuthProvider的authenticate方法末尾存在错误代码,无论认证成功与否,都会重新创建一个未认证的UserPassAuthToken并返回。这会导致AuthenticationManager判定认证失败,由于只有该Provider支持UserPassAuthToken类型,进而触发无限递归调用,最终引发栈溢出。
  2. 安全配置遗漏雇主Provider注册:SecConfig中仅注册了用户认证Provider,未将雇主认证Provider加入认证链,导致雇主认证路径无法生效。

修复步骤

1. 修复用户认证Provider的逻辑错误

修改UserPassAuthProvider的authenticate方法,移除末尾覆盖认证结果的错误代码:

@Component
public class UserPassAuthProvider implements AuthenticationProvider {

    @Autowired
    UserService userDetailsService;
    @Autowired
    PasswordEncoder passwordEncoder;

    @Override
    public UserPassAuthToken authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        UserPassAuthToken userPassAuthToken;
        UsersDetails user = userDetailsService.loadUserByUsername(username);
        if (user != null && password != null && passwordEncoder.matches(password, user.getPassword())) {
            userPassAuthToken = new UserPassAuthToken(username, user.getPassword(), user.getAuthorities());
            userPassAuthToken.setUsersDetails(user);
        } else {
            userPassAuthToken = new UserPassAuthToken(username, password);
            userPassAuthToken.setAuthenticated(false);
        }
        // 移除以下两行错误代码
        // userPassAuthToken = new UserPassAuthToken(username, password);
        // userPassAuthToken.setAuthenticated(false);
        return userPassAuthToken;
    }

    @Override
    public boolean supports(Class<?> authType) {
        return UserPassAuthToken.class.equals(authType);
    }
}

2. 在安全配置中注册雇主认证Provider

在SecConfig中注入雇主Provider并添加到认证链:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecConfig {

    @Autowired
    UserPassAuthProvider userPassAuthProvider;
    @Autowired
    EmployerAuthProvider employerAuthProvider; // 新增注入
    @Autowired
    UserPassAuthFilter userPassAuthFilter;
    @Autowired
    TokenAuthFilter tokenAuthFilter;
    @Autowired
    AuthPathPrivilege userPrivilege;
    @Autowired
    AuthPath authPath;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers(authPath.getAllNotAuthPath().iterator().next()).permitAll()
                .antMatchers(authPath.getAllAuthPath().iterator().next()).authenticated()
                .antMatchers(userPrivilege.getSuper_adminPrivilegePath().iterator().next()).hasAnyAuthority("SUPER_ADMIN")
                .antMatchers(userPrivilege.getAdminPrivilegePath().iterator().next()).hasAnyAuthority("ADMIN")
                .antMatchers(userPrivilege.getManagerPrivilegePath().iterator().next()).hasAnyAuthority("MANAGER")
                .antMatchers(userPrivilege.getUserPrivilegePath().iterator().next()).hasAnyAuthority("USER")
                .and().httpBasic()
                .and().sessionManagement().sessionCreationPolicy(STATELESS)
                .and().authenticationProvider(userPassAuthProvider)
                .authenticationProvider(employerAuthProvider) // 新增注册雇主Provider
                .addFilterAt(userPassAuthFilter , BasicAuthenticationFilter.class)
                .addFilterAfter(tokenAuthFilter , BasicAuthenticationFilter.class);

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

3. 确认雇主认证Token实现(补充)

确保EmployerPassAuthToken正确继承UsernamePasswordAuthenticationToken,结构与用户Token一致:

public class EmployerPassAuthToken extends UsernamePasswordAuthenticationToken {
    @Getter
    @Setter
    private UsersDetails usersDetails;

    public EmployerPassAuthToken(Object principal, Object credentials) {
        super(principal, credentials);
    }

    public EmployerPassAuthToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
        super(principal, credentials, authorities);
    }
}

4. 路径区分优化(可选)

确保用户和雇主过滤器的shouldNotFilter方法分别对应各自的登录路径,避免请求混淆。例如用户登录路径为/user/login,雇主为/employer/login,各自过滤器仅处理对应路径的请求。

问题原理补充

当AuthenticationManager调用Provider的authenticate方法后,若返回的Authentication对象isAuthenticated()为false,会继续尝试下一个支持该Token类型的Provider。由于UserPassAuthProvider错误地始终返回未认证Token,且只有它支持UserPassAuthToken,导致AuthenticationManager反复调用该Provider,形成无限递归,最终引发栈溢出。

内容的提问来源于stack exchange,提问作者keroles magdy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 23:50:29