带@Component注解的自定义额外AuthenticationProvider引发无限循环
你的问题核心是:添加NotUsedFakeAuthenticationProvider作为@Component后,认证失败触发无限循环,移除该注解则恢复正常,但你需要保留这个Provider用于其他业务。
根本原因
Spring Security会自动将所有标注@Component的AuthenticationProvider Bean注册到所有AuthenticationManager实例中——包括你手动构建的那个。虽然NotUsedFakeAuthenticationProvider的supports方法只处理FakeAuthenticationToken2,不会介入FakeAuthenticationToken的认证流程,但此时ProviderManager(AuthenticationManager的默认实现)在处理认证时会遍历所有已注册的Provider:
- 第一个Provider(
FakeAuthenticationProvider)抛出InsufficientAuthenticationException,被捕获并记录为最后异常 - 遍历到
NotUsedFakeAuthenticationProvider时,因不支持FakeAuthenticationToken被跳过 - 最终抛出的仍是
InsufficientAuthenticationException,但这个遍历过程导致异常处理逻辑发生变化:原本应由自定义过滤器的失败处理器处理的异常,被ExceptionTranslationFilter拦截,触发重定向到登录页面;而你的自定义过滤器又会拦截登录请求的POST提交,再次触发认证失败,形成无限循环。
同时,你的自定义过滤器未设置自定义失败处理器,默认的SimpleUrlAuthenticationFailureHandler会重定向到登录页面,进一步加剧了循环。
解决方案
方案1:手动控制AuthenticationManager的Provider注册
修改你手动构建AuthenticationManager的代码,清除Spring自动添加的所有Provider,只保留你需要的FakeAuthenticationProvider:
@Bean public AuthenticationManager getAuthenticationManager(HttpSecurity http, FakeAuthenticaitonProvider fakeAuthenticaitonProvider) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); // 清除自动注册的所有Provider authenticationManagerBuilder.getProviders().clear(); // 只添加当前过滤器需要的Provider authenticationManagerBuilder.authenticationProvider(fakeAuthenticaitonProvider); return authenticationManagerBuilder.build(); }
这样,你手动构建的AuthenticationManager只会包含FakeAuthenticationProvider,不会被NotUsedFakeAuthenticationProvider干扰,认证失败后会正常触发自定义过滤器的失败处理器。
方案2:给自定义过滤器设置自定义失败处理器
直接指定认证失败后的跳转逻辑,避免默认的登录页面重定向:
@Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { final FakeAuthenticationFilter fakeAuthenticationFilter = new FakeAuthenticationFilter(); fakeAuthenticationFilter.setAuthenticationManager(authenticationManager); // 设置自定义失败处理器,跳转到错误页面 fakeAuthenticationFilter.setAuthenticationFailureHandler((request, response, exception) -> { response.sendRedirect("/error"); }); http .addFilterBefore(fakeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/error").permitAll() // 允许匿名访问错误页面 .anyRequest().authenticated() ) .formLogin(withDefaults()); return http.build(); }
同时要确保错误页面/error允许匿名访问,否则跳转后会再次触发认证流程。
方案3:限定NotUsedFakeAuthenticationProvider的注册范围
如果NotUsedFakeAuthenticationProvider只在特定业务场景中使用,不要用@Component自动注册,而是在需要它的地方手动创建实例,或者通过@Bean注解仅注册到特定的AuthenticationManager中:
// 移除NotUsedFakeAuthenticationProvider上的@Component注解 public class NotUsedFakeAuthenticaitonProvider implements AuthenticationProvider { // ... 原有代码 } // 在需要使用它的配置类中手动注册 @Configuration public class OtherSecurityConfiguration { @Bean public AuthenticationManager otherAuthenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class); builder.authenticationProvider(new NotUsedFakeAuthenticaitonProvider()); return builder.build(); } }
这样既保留了该Provider的业务用途,又不会干扰主认证流程的AuthenticationManager。
内容的提问来源于stack exchange,提问作者LilRazi

