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

Spring Security OAuth2多资源服务器按Audience区分API权限问题求助

解决方案思路与实现建议

核心问题分析

你的问题本质是Spring Security默认的过滤器执行顺序导致:Bearer Token校验逻辑先于请求路径匹配,即便请求属于其他API的路径范围,当前过滤器链仍会先执行Audience校验,失败后直接抛出错误,不会流转到后续匹配的过滤器链。下面是两种落地性强的解决思路:


方案一:用securityMatcher()让路径匹配优先生效

这是官方推荐的最简方案,通过securityMatcher()为每个API的过滤器链指定精准路径匹配规则,Spring Security会在执行任何校验逻辑前先判断请求是否属于当前链的路径范围,不匹配则直接跳过当前链。

代码示例

@Bean
SecurityFilterChain apiV1FilterChain(HttpSecurity http) throws Exception {
    http
        // 先做路径匹配,不匹配直接跳过该过滤器链
        .securityMatcher("/api/v1/**")
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt
                // 配置该API要求的Audience
                .audience(aud -> aud.contains("api-v1-aud"))
            )
        );
    return http.build();
}

@Bean
SecurityFilterChain apiV2FilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/v2/**")
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2
            .jwt(jwt -> jwt
                .audience(aud -> aud.contains("api-v2-aud"))
            )
        );
    return http.build();
}

注意事项

  • securityMatcher()是Spring Security 5.4+引入的特性,优先级高于传统的requestMatchers(),能确保路径匹配逻辑最先执行。
  • 多个过滤器链会自动按路径匹配精度排序(精准路径优先),也可以通过@Order注解手动指定优先级(数字越小优先级越高)。

方案二:自定义Audience感知的AuthenticationManagerResolver

如果你的API路径和Audience没有强绑定关系,可以自定义解析器,根据Token中的Audience动态选择对应的校验逻辑,不匹配则让后续过滤器链处理。

代码示例

@Component
public class AudienceAwareAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {

    private final Map<String, AuthenticationManager> authManagerMap;

    // 基于同一个Issuer创建不同Audience的校验管理器
    public AudienceAwareAuthenticationManagerResolver(JwtDecoder jwtDecoder) {
        this.authManagerMap = Map.of(
            "api-v1-aud", createAudienceSpecificAuthManager(jwtDecoder, "api-v1-aud"),
            "api-v2-aud", createAudienceSpecificAuthManager(jwtDecoder, "api-v2-aud")
        );
    }

    private AuthenticationManager createAudienceSpecificAuthManager(JwtDecoder decoder, String requiredAud) {
        JwtAuthenticationProvider provider = new JwtAuthenticationProvider(decoder);
        provider.setJwtAuthenticationConverter(jwt -> {
            // 校验Token是否包含目标Audience
            if (!jwt.getAudience().contains(requiredAud)) {
                throw new OAuth2AuthenticationException(
                    new OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN, "Token audience does not match API requirement", null)
                );
            }
            return new JwtAuthenticationToken(jwt);
        });
        return provider::authenticate;
    }

    @Override
    public AuthenticationManager resolve(HttpServletRequest request) {
        String token = extractBearerToken(request);
        if (token == null) {
            return null;
        }

        try {
            // 提前解析Token获取Audience
            Jwt jwt = JwtDecoder.fromIssuerLocation("your-issuer-uri").decode(token);
            // 返回匹配Audience的校验管理器,无匹配则返回null让后续链处理
            for (Map.Entry<String, AuthenticationManager> entry : authManagerMap.entrySet()) {
                if (jwt.getAudience().contains(entry.getKey())) {
                    return entry.getValue();
                }
            }
            return null;
        } catch (JwtException e) {
            return null;
        }
    }

    private String extractBearerToken(HttpServletRequest request) {
        String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            return authHeader.substring(7);
        }
        return null;
    }
}

配置使用自定义解析器

@Bean
SecurityFilterChain sharedFilterChain(HttpSecurity http, AudienceAwareAuthenticationManagerResolver resolver) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
        .oauth2ResourceServer(oauth2 -> oauth2
            .authenticationManagerResolver(resolver)
        );
    return http.build();
}

注意事项

  • 可以给Token解析逻辑加入缓存,避免重复解析影响性能。
  • 如果所有Audience都不匹配,最终会返回401错误,可通过exceptionHandling()配置自定义异常处理器优化错误提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:55:06