【问题标题】:OAuth2RestTemplate Bearer Token TypeOAuth2RestTemplate 承载令牌类型
【发布时间】:2026-02-04 14:05:01
【问题描述】:

我正在使用带有 OAuth2RestTemplate 的 Spring Security OAuth2 来实现 OAuth 2.0 安全 REST API 的客户端。流程通过步骤成功获取访问令牌:

response.statusCode = 200
response.body = {"access_token":"9b90f8a84b939b8437a4fbaa8fff0052839cf6f5","expires_in":3600,"token_type":"bearer","scope":" read write","refresh_token":"e164f317a1708c3664025e9e56ce605cfe710474”}

但是,当代码尝试使用 OAuth2RestTemplate 访问受保护的资源时,响应不成功:

GET request for "https://redacted.com/api/v1/clients" resulted in 400 (Bad Request)

这是因为 DefaultOAuth2RequestAuthenticator 使用与 access_token 一起返回的 token_type 值(“bearer”)来形成对受保护资源的请求的 Authentication 标头:

request.getHeaders().set("Authorization", String.format("%s %s", tokenType, accessToken.getValue()));

这会导致请求授权标头中的凭据字段以字符串“bearer”为前缀:

Request Header: "Authorization" "bearer a8f18cb3173c4cbbea44f4495dd5e5662156c391"

但是,根据 OAuth 2.0 Bearer Token Usage spec 2.1 Authorization Request Header 字段,凭证字段的格式为:

credentials = "Bearer" 1*SP b64token

请注意,在规范中,“Bearer”是大写的。这意味着请求头应该是:

Request Header: "Authorization" "Bearer a8f18cb3173c4cbbea44f4495dd5e5662156c391"

显然,我们发送请求以实现规范的提供者狭隘地实现了规范,因为大小写问题(“bearer”与“Bearer”)是导致对受保护资源的请求失败的原因。当我更改 DefaultOAuth2RequestAuthenticator 以强制前缀为标题大小写时,请求成功:

if ("bearer".equals(tokenType)) {
    tokenType = "Bearer";
}

Header key = Authorization values = Bearer 8efd4381f3470660291e700e4927012f288ad66c,
Sending GET request to https://redacted.com/api/v1/clients
Received "200 OK" response for GET request to https://redacted.com/api/v1/clients: [[{"id":"999999999","client_user_id":"a1b2c3d4e5f6"...

我真的不想拥有自己的 Spring Security OAuth2 分支来解决这个问题。这对任何人都有什么作用?我错过了什么吗?

【问题讨论】:

    标签: spring spring-security oauth oauth-2.0


    【解决方案1】:

    事实证明,它实际上并不适合很多人。 spring-security-oauth 问题列表中有一个未解决的问题来解决这个问题(当前里程碑是 2.2.1):

    Use OAuth2 token_type equal "Bearer" by default everywhere #457

    标题可能有点误导,但 cmets 概述了这个问题并建议了解决方法,这些都是覆盖 OAuth2 令牌处理类之一的变体,以执行我对 DefaultOAuth2RequestAuthenticator 所做的修复。

    【讨论】:

      【解决方案2】:

      我通过这些代码解决了这个问题

          @Bean
      public OAuth2RestOperations restTemplate(){
      
          AccessTokenRequest accessTokenRequest = new DefaultAccessTokenRequest();
          OAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext(accessTokenRequest);
          OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate( resource(),oAuth2ClientContext );
          oAuth2RestTemplate.setAuthenticator(new DefaultOAuth2RequestAuthenticator(){
              @Override
              public void authenticate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext clientContext, ClientHttpRequest request) {
                  clientContext.setAccessToken(new DefaultOAuth2AccessToken(clientContext.getAccessToken()){
                      @Override
                      public String getTokenType() {
                          if ("bearer".equals(super.getTokenType())) {
                              return "Bearer";
                          }
                          return super.getTokenType();
                      }
                  });
                  OAuth2AccessToken accessToken = clientContext.getAccessToken();
                  super.authenticate(resource, clientContext, request);
              }
          });
      

      【讨论】: