You need to enable JavaScript to run this app. Spring Security单个REST端点如何支持OAuth2 JWT与ApiKey双认证
优惠活动
大模型
产品
解决方案
定价
更多

Spring Security单个REST端点如何支持OAuth2 JWT与ApiKey双认证

问题根因

双SecurityConfigurerAdapter方案失效原因

两个独立的WebSecurityConfigurerAdapter会各自生成一套完整的Spring Security过滤链,Order值更小的链路会优先匹配请求。当Order=1的ApiKey配置匹配到/api/special/url路径后,会在这套链路内完成全流程处理,Order=2的OAuth2链路根本不会被触发,两套认证逻辑天然互斥。

单HttpSecurity方案失效原因

自定义的ApiKeyAuthenticationFilter继承自AbstractAuthenticationProcessingFilter,父类默认的失败处理逻辑是直接中断过滤链:一旦ApiKey认证抛出异常(比如请求未携带ApiKey、携带的是JWT凭证),就会调用默认的失败处理逻辑直接返回401,后续排在过滤器链靠后的BearerTokenAuthenticationFilter没有执行机会,因此JWT认证流程永远不会被触发。
同时原方案中自定义的ProviderManager仅注册了ApiKey认证Provider,与全局负责JWT认证的管理器相互隔离,进一步加剧了逻辑不通的问题。

正确实现步骤

1. 改造ApiKey认证过滤器

核心规则:只有当请求中确实携带ApiKey格式的凭证时,才执行ApiKey认证校验;如果请求无ApiKey凭证,直接放行过滤链,交给后续JWT认证流程处理,不抛异常中断链路。
改造后的核心代码:

@Slf4j
public class ApiKeyAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    public ApiKeyAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher("/api/special/url/**", "GET"));
        this.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
        String authHeader = request.getHeader("Authorization");
        // 非ApiKey格式的请求直接返回空认证Token,不触发认证逻辑
        if (authHeader == null || !authHeader.startsWith("ApiKey ")) {
            return new ApiKeyAuthenticationToken();
        }
        // 提取ApiKey凭证执行校验
        String apiKey = authHeader.substring(7);
        ApiKeyAuthenticationToken token = new ApiKeyAuthenticationToken(apiKey);
        return getAuthenticationManager().authenticate(token);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        String authHeader = request.getHeader("Authorization");
        // 仅当请求携带ApiKey凭证但校验失败时,才中断流程返回401
        if (authHeader != null && authHeader.startsWith("ApiKey ")) {
            super.unsuccessfulAuthentication(request, response, failed);
            return;
        }
        // 无ApiKey凭证的请求直接放行,交由后续JWT认证流程处理
        chain.doFilter(request, response);
    }
}

2. 调整Spring Security配置

  • 移除仅包含ApiKeyProvider的自定义ProviderManager,将ApiKeyAuthenticationProvider注册到全局认证管理器,保证两套认证逻辑可被同一个AuthenticationManager识别处理。
  • 保持ApiKeyAuthenticationFilter在BearerTokenAuthenticationFilter之前的位置。
    改造后的配置代码:
@EnableWebSecurity(debug = true)
@Profile("securityOn")
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ApiKeyAuthenticationProvider apiKeyAuthenticationProvider;
    @Autowired
    private OAuth2ResourceServerProperties resourceServerProperties;

    // 将自定义ApiKey认证Provider注册到全局认证管理器
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(apiKeyAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/actuator/health").permitAll()
                .antMatchers("/**").authenticated()
            .and()
            // 注入改造后的ApiKey过滤器
            .addFilterBefore(
                new ApiKeyAuthenticationFilter(authenticationManager()),
                BearerTokenAuthenticationFilter.class
            )
            // 保持原有JWT资源服务器配置
            .oauth2ResourceServer()
                .jwt()
                .jwkSetUri(this.resourceServerProperties.getJwt().getJwkSetUri());
    }
}

3. 链路逻辑验证

改造后全链路执行逻辑完全符合预期:

  • 携带合法ApiKey访问/api/special/url:ApiKey过滤器校验通过,完成认证,后续JWT过滤器检测到上下文已有有效认证信息直接跳过,请求放行。
  • 携带合法JWT访问/api/special/url:ApiKey过滤器检测到请求无ApiKey凭证,直接放行,后续BearerTokenAuthenticationFilter完成JWT校验,请求放行。
  • 携带非法ApiKey/JWT访问任意路径:对应认证校验失败,返回401。
  • 携带合法JWT访问非特殊路径:ApiKey过滤器的路径匹配规则仅覆盖/api/special/url,请求直接跳过该过滤器,由JWT认证流程处理,与原有行为完全一致。

内容的提问来源于stack exchange,提问作者mmelsen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.25 01:57:01