Spring Security多路径认证冲突:API Key过滤器误影响表单登录
问题原因
你遇到的核心问题是**ApiKeyAuthFilter被Spring注册为全局Servlet过滤器**,而非仅属于/api/**对应的SecurityFilterChain。即使请求不匹配/api/**路径,这个过滤器也会在所有SecurityFilterChain执行前被调用,直接返回401响应,导致表单登录流程根本没机会触发。
日志里显示的是SecurityFilterChain的匹配情况,但全局过滤器的执行优先级高于Spring Security的过滤器链,所以日志记录的环节已经是过滤器执行之后的流程了。
解决方案
要让ApiKeyAuthFilter仅作用于/api/**路径的过滤器链,需要做以下修改:
1. 移除过滤器类上的@Component注解
首先去掉ApiKeyAuthFilter类上的@Component,避免Spring自动将其注册为全局过滤器:
// 移除@Component注解 public class ApiKeyAuthFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { var request = (HttpServletRequest) servletRequest; var response = (HttpServletResponse) servletResponse; var apiKey = request.getHeader("ApiKey"); if (apiKey == null) { unauthorized(response); return; } if (!apiKey.equals("sillytest")) { unauthorized(response); return; } filterChain.doFilter(request, response); } // 其他方法保持不变 }
2. 在API过滤器链中手动添加该过滤器
有两种方式实现:
方式一:通过@Bean单独定义过滤器实例
在配置类中添加ApiKeyAuthFilter的@Bean,让Spring管理其实例,然后通过构造注入到配置类中:
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class MyLinksConfiguration { // 构造注入保持不变 private final ApiKeyAuthFilter apiKeyAuthFilter; private final UnauthorizedHandler unauthorizedHandler; // 添加这个Bean定义 @Bean public ApiKeyAuthFilter apiKeyAuthFilter() { return new ApiKeyAuthFilter(); } // 其他Bean定义保持不变... @Bean @Order(1) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { return http.securityMatcher("/api/**") .addFilterBefore(apiKeyAuthFilter, ChannelProcessingFilter.class) .exceptionHandling(configurer -> configurer.authenticationEntryPoint(unauthorizedHandler)) .cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .build(); } // 表单登录过滤器链保持不变... }
方式二:直接在过滤器链中创建实例
如果不需要Spring管理ApiKeyAuthFilter的生命周期,也可以直接在apiFilterChain中new出过滤器实例:
@Bean @Order(1) public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { return http.securityMatcher("/api/**") .addFilterBefore(new ApiKeyAuthFilter(), ChannelProcessingFilter.class) .exceptionHandling(configurer -> configurer.authenticationEntryPoint(unauthorizedHandler)) .cors(AbstractHttpConfigurer::disable) .csrf(AbstractHttpConfigurer::disable) .build(); }
额外说明
如果不想修改过滤器的@Component注解,也可以在过滤器的doFilter方法开头添加路径判断,仅处理/api/**的请求:
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { var request = (HttpServletRequest) servletRequest; // 仅处理/api开头的请求,其他请求直接放行 if (!request.getRequestURI().startsWith("/api/")) { filterChain.doFilter(request, servletResponse); return; } // 原有API Key校验逻辑... }
但这种方式不如前两种优雅,因为过滤器仍会被全局调用,只是跳过了非/api路径的处理。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

