You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security整合OAuth2与API Key过滤器的认证异常排查

解决方案思路

核心问题分析

你当前的配置中,apiKeyAuthFilter被无条件添加到过滤器链,导致即使JWT认证通过,过滤器仍会执行并校验X-API-KEY,最终触发认证失败。Spring Security的认证逻辑是只要有一个认证方式通过,后续认证过滤器不会被触发,但你的自定义过滤器没有集成到Spring Security的认证流程中,而是作为独立过滤器强制执行。

具体修复方案

方案一:将API Key认证集成到Spring Security的认证管理器

  1. 自定义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);
    }
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:32:12