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

Spring多租户场景下获取请求携带的OAuth2/OIDC Access Token问题

解决Spring OAuth2/OIDC同时作为客户端和资源服务器时无法获取Access Token的问题

问题背景

我正尝试将原单用户本地系统改造为云环境下的多租户系统,已基于OAuth2、OIDC和PKCE实现跳转至外部认证提供商PingOne的功能。现在希望每个请求携带Access Token,以便根据令牌返回对应数据,但Spring默认仅传递ID Token,怀疑这与系统同时作为客户端和资源服务器有关。

已尝试操作:

  • 通过Postman验证ID Token、Refresh Token和Access Token均可正常发放;
  • 尝试通过@AuthenticationPrincipal OidcUser oidcUser从请求中获取令牌,但仅能拿到ID Token;
  • 通过OAuth2AuthorizedClientService获取令牌,同样只能得到ID Token。

相关依赖:spring-boot-starter-web、spring-boot-starter-security、spring-boot-starter-thymeleaf、spring-boot-starter-web-services、spring-boot-starter-oauth2-resource-server


核心原因

当Spring应用同时承担OAuth2客户端(处理登录跳转)和资源服务器(验证请求令牌)角色时,默认客户端配置只会将ID Token存入认证上下文,Access Token需要显式配置才能被保留和传递。


解决方案步骤

1. 配置OAuth2客户端保存完整授权信息

修改Spring Security配置类,确保客户端请求所需权限范围,同时让授权客户端服务保存包含Access Token的完整信息:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2Login(oauth2 -> oauth2
                .authorizedClientService(authorizedClientService())
                .userInfoEndpoint(userInfo -> userInfo.oidcUserService(oidcUserService())))
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
        return http.build();
    }

    @Bean
    public OAuth2AuthorizedClientService authorizedClientService(
            ClientRegistrationRepository clientRegistrationRepository) {
        return new DefaultOAuth2AuthorizedClientService(clientRegistrationRepository);
    }

    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();
        return userRequest -> {
            // 加载包含Access Token的授权客户端信息
            OAuth2AuthorizedClient authorizedClient = authorizedClientService().loadAuthorizedClient(
                    userRequest.getClientRegistration().getRegistrationId(),
                    userRequest.getPrincipal().getName());
            // 获取默认用户信息
            OidcUser oidcUser = delegate.loadUser(userRequest);
            // 将Access Token存入用户属性,方便后续调取
            oidcUser.getAttributes().put("access_token", authorizedClient.getAccessToken().getTokenValue());
            return oidcUser;
        };
    }
}

2. 在请求中携带Access Token

前端页面(Thymeleaf)

从认证用户属性中取出Access Token,在API请求头中携带:

<script>
    const accessToken = /*[[${#authentication.principal.attributes['access_token']}]]*/ '';
    // 发起接口请求时添加Authorization头
    fetch('/api/tenant-data', {
        headers: {
            'Authorization': `Bearer ${accessToken}`
        }
    });
</script>

后端内部调用

通过OAuth2AuthorizedClientService直接获取Access Token:

@Service
public class TenantDataService {
    private final OAuth2AuthorizedClientService authorizedClientService;

    public TenantDataService(OAuth2AuthorizedClientService authorizedClientService) {
        this.authorizedClientService = authorizedClientService;
    }

    public String getTenantSpecificData(Authentication authentication) {
        OAuth2AuthorizedClient authorizedClient = authorizedClientService.loadAuthorizedClient(
                "pingone", // 你的PingOne客户端注册ID
                authentication.getName());
        String accessToken = authorizedClient.getAccessToken().getTokenValue();
        // 用Access Token调用下游服务或查询租户数据
        return fetchDataWithToken(accessToken);
    }
}

3. 配置资源服务器解析Access Token

确保资源服务器能正确解析PingOne颁发的Access Token,调整JWT转换器适配PingOne的Token字段:

@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
    grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); // 对应PingOne Token中的权限字段

    JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
    converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
    return converter;
}

4. 检查客户端注册范围

在配置文件中确认客户端注册包含openid及业务所需的权限范围(比如read、write):

spring:
  security:
    oauth2:
      client:
        registration:
          pingone:
            client-id: 你的PingOne客户端ID
            client-secret: 你的PingOne客户端密钥
            scope: openid, read, write
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          pingone:
            issuer-uri: https://你的PingOne颁发者域名.com
            user-name-attribute: sub

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 21:01:04