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

Spring Boot OAuth2ResourceServer如何支持1-2个不同签发方的JWT令牌?

问题:Spring Boot OAuth2ResourceServer 多签发方令牌适配方案

目前难以找到适配自身场景的解决方案,相关文档匮乏,仅找到一篇略微相关的Stack Overflow帖子。

场景概述

我们所有Spring Boot微服务均配置了基于内部签发方(Provider A)的OAuth2ResourceServer,仅允许其他配置了OAuth2 WebClient的微服务或员工访问,这部分运行正常。

但问题在于,部分微服务端点需要额外的Provider B令牌来授权客户(例如访问或修改其数据)。所有发送至微服务的HTTP请求均携带Authorization头(对应Provider A),部分请求还会携带CUSTOMER_TOKEN头(对应Provider B)。

我的问题

如何配置Spring Boot,使OAuth2ResourceServer支持来自两个不同签发方的1个或2个JWT令牌?是否有相关指引或更优方案?

已尝试方案

目前我尝试了几种方案,但都感觉不够规范,怀疑未找到正确方向。我想出的“最佳”方案包含配置自定义Customizer<OAuth2ResourceServerConfigurer<HttpSecurity>>、自定义AuthenticationManagerResolver、自定义AccessDeniedHandler、自定义AuthenticationEntrypoint,以及两个分别配置对应JWK Set URI和Issuer URI的JwtDecoder:

@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity)
    throws Exception {
  return httpSecurity.csrf()
      .disable()
      .cors()
      .configurationSource(corsConfigurationSource())
      .and()
      .sessionManagement()
      .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      .and()
      .authorizeRequests()
      .antMatchers(WHITELIST)
      .permitAll()
      .and()
      .authorizeRequests()
      .anyRequest()
      .authenticated()
      .and()
      .oauth2ResourceServer(configurer -> 
        configurer
            .authenticationManagerResolver(authenticationManagerResolver())
            .authenticationEntryPoint(authenticationEntryPoint())
            .accessDeniedHandler(accessDeniedHandler());
      )
      .build();
}

@Bean
public AccessDeniedHandler accessDeniedHandler(){
  return (request, response, accessDeniedException) -> {
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    PrintWriter writer = response.getWriter();
    writer.write("\"message\": \"access denied\"");
    writer.flush();
  };
}

@Bean
public AuthenticationEntryPoint authenticationEntryPoint(){
  return (request, response, authenticationException) -> {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    response.setCharacterEncoding(StandardCharsets.UTF_8.name());
    PrintWriter writer = response.getWriter();
    writer.write("\"message\": \"unauthorized\"");
    writer.flush();
  };
}

@Bean
protected AuthenticationManagerResolver<HttpServletRequest>
    authenticationManagerResolver() {
  return request ->
      authentication -> {
        List<GrantedAuthority> authorities = new ArrayList<>(); // merged authorities
        Map<String, Object> attributes = new HashMap<>(); // merged attributes
        
        Authentication providerA = authenticate(request, OAuth2Provider.PROVIDER_A, authorities, attributes);
        
        if (request.getHeader(OAuth2Provider.PROVIDER_B.getHeader()) == null) {
          // If Provider B token is not in the request, the user won't have the appropriate role
          // to access endpoints annotated with @RoleAllowed(CUSTOMER_ROLE)
          return providerA;
        }
        
        return authenticate(request, OAuth2Provider.PROVIDER_B, authorities, attributes);
      };
}

public final Authentication authenticate(
    HttpServletRequest request,
    OAuth2Provider oAuth2Provider,
    List<GrantedAuthority> authorities,
    Map<String, Object> attributes) {
  Jwt jwt = decodeAccessToken(request, oAuth2Provider); // JwtDecoder + error handling

  authorities.add(new SimpleGrantedAuthority(oAuth2Provider.getRole()));
  authorities.addAll(getTokenAuthorities(jwt));

  Map<String, Object> providerAttributes = new HashMap<>(jwt.getClaims());
  providerAttributes.put("token", jwt);
  attributes.put(oAuth2Provider.getName(), providerAttributes);

  OAuth2AuthenticatedPrincipal principal =
      new DefaultOAuth2AuthenticatedPrincipal(attributes, authorities);
  
  OAuth2AccessToken accessToken =
      new OAuth2AccessToken(
          TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt());

  return new BearerTokenAuthentication(principal, accessToken, authorities);
}

该方案虽能运行,但感觉不够规范,因为需要手动返回401或403状态码,这在安全处理中并非理想做法。此外,Spring 3.x已弃用HttpServletRequest,更让我觉得该方案不够合理,希望找到更优解决方案。

内容的提问来源于stack exchange,提问作者G.T.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 16:18:08