新增Employer AuthenticationProvider后调用authenticate出现StackOverflowError
问题描述
项目包含普通用户和雇主两条认证路径,最初仅实现普通用户的认证过滤器与AuthenticationProvider时功能正常。新增雇主AuthenticationProvider后,调用AuthenticationManager.authenticate(userPassAuthToken)时触发java.lang.StackOverflowError: null异常,调试时卡在过滤器的该调用行,且程序不会进入用户认证Provider的逻辑。
核心问题定位
- 用户认证Provider逻辑错误:
UserPassAuthProvider的authenticate方法末尾存在错误代码,无论认证成功与否,都会重新创建一个未认证的UserPassAuthToken并返回。这会导致AuthenticationManager判定认证失败,由于只有该Provider支持UserPassAuthToken类型,进而触发无限递归调用,最终引发栈溢出。 - 安全配置遗漏雇主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

