Spring Security整合OAuth2与API Key过滤器的认证异常排查
解决方案思路
核心问题分析
你当前的配置中,apiKeyAuthFilter被无条件添加到过滤器链,导致即使JWT认证通过,过滤器仍会执行并校验X-API-KEY,最终触发认证失败。Spring Security的认证逻辑是只要有一个认证方式通过,后续认证过滤器不会被触发,但你的自定义过滤器没有集成到Spring Security的认证流程中,而是作为独立过滤器强制执行。
具体修复方案
方案一:将API Key认证集成到Spring Security的认证管理器
- 自定义API Key认证令牌、提供者和过滤器
// 自定义API Key认证令牌 public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken { private final String apiKey; public ApiKeyAuthenticationToken(String apiKey) { super(Collections.emptyList()); this.apiKey = apiKey; setAuthenticated(false); } public ApiKeyAuthenticationToken(String apiKey, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.apiKey = apiKey; super.setAuthenticated(true); } @Override public Object getCredentials() { return apiKey; } @Override public Object getPrincipal() { return apiKey; } } // API Key认证提供者 @Component public class ApiKeyAuthenticationProvider implements AuthenticationProvider { @Value("${app.api-key}") private String validApiKey; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String apiKey = authentication.getCredentials().toString(); if (validApiKey.equals(apiKey)) { return new ApiKeyAuthenticationToken(apiKey, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))); } throw new BadCredentialsException("Invalid API Key"); } @Override public boolean supports(Class<?> authentication) { return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication); } } // API Key过滤器 public class ApiKeyAuthenticationFilter extends AbstractAuthenticationProcessingFilter { private static final String API_KEY_HEADER = "X-API-KEY"; public ApiKeyAuthenticationFilter() { super(new AntPathRequestMatcher("/**")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { String apiKey = request.getHeader(API_KEY_HEADER); if (apiKey == null || apiKey.isBlank()) { return null; // 没有API Key时不执行认证,交给后续过滤器处理 } ApiKeyAuthenticationToken authRequest = new ApiKeyAuthenticationToken(apiKey); return getAuthenticationManager().authenticate(authRequest); } }
- 修改SecurityFilterChain配置,集成多认证提供者
@Bean public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { ApiKeyAuthenticationFilter apiKeyFilter = new ApiKeyAuthenticationFilter(); apiKeyFilter.setAuthenticationManager(authenticationManager); http.authenticationProvider(apiKeyAuthenticationProvider()) .addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .headers(h -> h.httpStrictTransportSecurity(c -> c.requestMatcher(AnyRequestMatcher.INSTANCE))) .oauth2ResourceServer(server -> server.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtGrantedAuthoritiesConverter()).decoder(jwtDecoder()) )); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); }
方案二:修改现有apiKeyFilter,仅在无JWT令牌时触发
在现有apiKeyAuthFilter中添加判断逻辑,跳过已认证或存在JWT令牌的请求:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 检查是否已通过JWT认证 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.isAuthenticated()) { filterChain.doFilter(request, response); return; } // 检查是否存在JWT令牌(适配你的令牌携带方式) String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } // 原有API Key校验逻辑 String apiKey = request.getHeader("X-API-KEY"); if (apiKey == null || !validApiKey.equals(apiKey)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid or missing API Key"); return; } // 设置认证上下文 SecurityContextHolder.getContext().setAuthentication(new ApiKeyAuthenticationToken(apiKey, Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")))); filterChain.doFilter(request, response); }
关键原理说明
Spring Security的过滤器链按顺序执行,只有当前一个认证过滤器未成功认证时,后续过滤器才会继续执行。将自定义认证逻辑集成到Spring Security的AuthenticationManager体系中,框架才会自动处理多认证方式的优先级和跳过逻辑。
内容的提问来源于stack exchange,提问作者Twin322
相关产品推荐
相关产品推荐

