Spring Security 6中自定义AuthenticationManager未生效问题排查
在Spring Boot 3.1.x/Spring Security 6.1.0应用中,我需要支持多种自定义认证方式,于是创建了包含两个自定义AuthenticationProvider的AuthenticationManager,但这些AuthenticationProvider并未被调用,请求直接返回403状态码。
我的安全配置代码
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { var builder = http.getSharedObject(AuthenticationManagerBuilder.class); // 两个未被调用的AuthenticationProvider实现类 builder.authenticationProvider(new AuthProvider1()); builder.authenticationProvider(new AuthProvider2()); var authManager = builder.build(); return http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) .authenticationManager(authManager) .build(); } }
请求debug日志输出
13:31:51.540 [http-nio-8080-exec-1] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet' 13:31:51.540 [http-nio-8080-exec-1] INFO o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet' 13:31:51.540 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Detected StandardServletMultipartResolver 13:31:51.540 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Detected AcceptHeaderLocaleResolver 13:31:51.540 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Detected FixedThemeResolver 13:31:51.541 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@3b1247f7 13:31:51.541 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Detected org.springframework.web.servlet.support.SessionFlashMapManager@6cd3032 13:31:51.541 [http-nio-8080-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data 13:31:51.541 [http-nio-8080-exec-1] INFO o.s.web.servlet.DispatcherServlet - Completed initialization in 1 ms 13:31:51.547 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing GET /products 13:31:51.550 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext 13:31:51.552 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext 13:31:51.562 [http-nio-8080-exec-1] DEBUG o.s.s.w.s.HttpSessionRequestCache - Saved request http://localhost:8080/products to session 13:31:51.562 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.Http403ForbiddenEntryPoint - Pre-authenticated entry point called. Rejecting access 13:31:51.563 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext 13:31:51.564 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store empty SecurityContext 13:31:51.564 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request 13:31:51.566 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - Securing GET /error 13:31:51.566 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Set SecurityContextHolder to empty SecurityContext 13:31:51.566 [http-nio-8080-exec-1] DEBUG o.s.s.w.a.AnonymousAuthenticationFilter - Set SecurityContextHolder to anonymous SecurityContext 13:31:51.566 [http-nio-8080-exec-1] DEBUG o.s.security.web.FilterChainProxy - Secured GET /error 13:31:51.572 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store anonymous SecurityContext 13:31:51.573 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.HttpSessionSecurityContextRepository - Did not store anonymous SecurityContext 13:31:51.573 [http-nio-8080-exec-1] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - Cleared SecurityContextHolder to complete request
我想知道为什么自定义AuthenticationManager和AuthenticationProvider没被使用?我知道有其他替代方案(比如自定义安全过滤器),但希望用这种简洁的多认证策略方案。
问题原因
从日志能看到请求走到了Http403ForbiddenEntryPoint,核心问题是没有配置任何认证过滤器触发AuthenticationManager的调用。
在Spring Security 6.x中,仅配置AuthenticationManager并绑定到HttpSecurity是不够的——AuthenticationManager需要被某个认证过滤器(比如UsernamePasswordAuthenticationFilter、自定义Token过滤器)调用,才能执行认证逻辑。你的配置只要求所有请求必须认证,但没告诉Spring Security如何获取认证信息、触发认证流程,所以直接判定为未认证返回403。
另外,你通过http.getSharedObject(AuthenticationManagerBuilder.class)构建的AuthenticationManager,因为没有对应过滤器触发,根本不会被执行。
解决方案
要让自定义AuthenticationProvider生效,必须添加对应的认证过滤器或配置认证入口触发AuthenticationManager,以下是两种符合需求的简洁方案:
方案1:配置HttpBasic认证(快速验证)
如果你的自定义AuthenticationProvider支持UsernamePasswordAuthenticationToken,可以配置HttpBasic认证触发流程:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { AuthenticationManager authManager = http.getSharedObject(AuthenticationManagerBuilder.class) .authenticationProvider(new AuthProvider1()) .authenticationProvider(new AuthProvider2()) .build(); return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated()) .authenticationManager(authManager) .httpBasic(withDefaults()) // 添加HttpBasic过滤器,触发AuthenticationManager调用 .build(); } }
当请求携带Basic Auth头时,HttpBasicAuthenticationFilter会调用你的AuthenticationManager,进而触发两个自定义Provider的认证逻辑。
方案2:自定义认证过滤器(灵活适配自定义认证)
如果是自定义Token等非HttpBasic的认证方式,编写自定义过滤器获取认证信息并触发认证:
@Component public class CustomAuthFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; public CustomAuthFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求中获取自定义认证信息,比如请求头、参数 String authToken = request.getHeader("X-Auth-Token"); if (authToken != null) { // 构建自定义Authentication对象,需与你的Provider的supports方法匹配 Authentication authRequest = new CustomAuthenticationToken(authToken); // 调用AuthenticationManager执行认证 Authentication authResult = authenticationManager.authenticate(authRequest); // 将认证结果存入SecurityContext SecurityContextHolder.getContext().setAuthentication(authResult); } filterChain.doFilter(request, response); } }
然后在SecurityConfig中注册过滤器:
@Configuration @EnableWebSecurity public class SecurityConfig { private final CustomAuthFilter customAuthFilter; public SecurityConfig(CustomAuthFilter customAuthFilter) { this.customAuthFilter = customAuthFilter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { AuthenticationManager authManager = http.getSharedObject(AuthenticationManagerBuilder.class) .authenticationProvider(new AuthProvider1()) .authenticationProvider(new AuthProvider2()) .build(); return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated()) .authenticationManager(authManager) .addFilterBefore(customAuthFilter, UsernamePasswordAuthenticationFilter.class) // 添加自定义过滤器 .build(); } }
额外注意事项
- 每个AuthenticationProvider的
supports(Class<?> authentication)方法必须返回true,才能被AuthenticationManager调用,请确保你的AuthProvider1和AuthProvider2的该方法正确实现,匹配传入的Authentication类型。 - 如果是无状态认证(比如基于Token),可以禁用匿名认证和会话管理,避免默认匿名上下文干扰:
http.anonymous(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
内容的提问来源于stack exchange,提问作者Julius

