提问者:小点点

Spring Boot授权服务器Google OAuth2/OpenId Connect应与access_token或id_token一起工作?


关于我应该通过access_token还是id_token访问我的Spring Boot资源服务器,我有点困惑。

首先,让我快速解释一下我的设置:

  • Spring Boot应用程序作为OAuth 2.0资源服务器。这是按照Spring文档中的描述配置的:JWT的最小配置此应用程序提供安全的@Controller,它将为JavaScript SPA(例如React)提供数据
  • Google的OAuth 2.0 AP/OpenID Connect已配置(凭据、客户端ID、客户端密钥)
  • 将用户登录到Google并向Spring Boot资源服务器请求安全数据的JavaScript SPA应用程序(例如React)。这些请求包括登录用户的授权标头(带有从Google获得的不记名令牌)。
  • 出于开发目的,我还使用Postman向Spring Boot资源服务器发出请求

我可以轻松地配置Postman以从Google获取令牌。来自 Google 的此令牌响应包括access_tokenid_token范围expries_intoken_type的值。

但是,当 Postman 尝试将检索到的令牌的 access_token 字段中的值用作授权标头中的持有者时,我对资源服务器的请求被拒绝

我能够成功访问受保护@Controllers的唯一方法是使用 id_token 作为授权标头中的持有者。

我是否应该使用id_token作为授权标头中的承载?还是我应该使用access_token

一些其他相关信息:

  • id_token的值是JWT令牌。access_token的值不是JWT令牌。我知道这一点,因为我可以解码jwt.io上的id_token,但它无法解码access_token的值。此外,当我将access_token作为Authorization标头中的承载发送时,Spring Boot Resource Server出现以下故障:

尝试解码 JWT 时出错:无效的不安全/JWS/JWE 标头:无效的 JSON:位置 2 处的意外令牌 ɭ。

  • 这篇博客文章“了解身份令牌”介绍了以下内容:

您不应该使用身份令牌来授权对API的访问。要访问API,您应该使用OAuth的访问令牌,这些令牌仅适用于受保护的资源(API),并内置了范围。

  • 查看使用 OAuth2 资源服务器的 spring-security-samples,我看到硬编码access_token(用于测试目的)的值确实是一个有效的 JWT。与从谷歌返回的access_token相反,它不是 JWT。

总而言之:

  • 我可以使用从Google获得的id_token值访问我的Spring Boot资源服务器。access_token的值不是JWT,Spring Boot无法解析
  • 我的理解、配置或其他方面有问题吗?谷歌的OpenId Connect在access_token的工作方式上是否有所不同

如果需要,很乐意澄清或添加更多信息。感谢您的考虑和耐心等待!


共1个答案

匿名用户

在我看来,你提到的博客文章是正确的,我相信OpenIDConnect1.0规范并不打算将id_token用于访问目的。

和您一样,我也希望将Google用作授权服务器,因为Spring Security与Google一起作为提供社交登录的常用OAuth2提供者。然而,事实并非如此,我相信这并不是真的有意为之,因为谷歌并不是你真正的授权服务器。例如,我不相信您可以将Google配置为使用域特定应用程序的范围/权限/权限。这与Okta不同,Okta有许多选项可用于在自己的租户中配置内容。

我实际上建议检查Spring Authorization Server,并将Google配置为联合身份提供者。我目前正在为此制作一个示例,它将在下周左右发布(请参阅此分支)。

话虽如此,如果您仍然对使用Google访问令牌对资源服务器进行身份验证的简单用例感兴趣,您需要提供自己的不透明令牌内省器,该内省器使用Google的令牌信息endpoint。它与Spring Security的预期不匹配,因此有点牵扯。

@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        http
            .authorizeRequests((authorizeRequests) -> authorizeRequests
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken);
        // @formatter:on

        return http.build();
    }

    @Bean
    public OpaqueTokenIntrospector introspector() {
        return new GoogleTokenIntrospector("https://oauth2.googleapis.com/tokeninfo");
    }

}
public final class GoogleTokenIntrospector implements OpaqueTokenIntrospector {
    private final RestTemplate restTemplate = new RestTemplate();
    private final String introspectionUri;

    public GoogleTokenIntrospector(String introspectionUri) {
        this.introspectionUri = introspectionUri;
    }

    @Override
    public OAuth2AuthenticatedPrincipal introspect(String token) {
        RequestEntity<?> requestEntity = buildRequest(token);
        try {
            ResponseEntity<Map<String, Object>> responseEntity = this.restTemplate.exchange(requestEntity, new ParameterizedTypeReference<>() {});
            // TODO: Create and return OAuth2IntrospectionAuthenticatedPrincipal based on response...
        } catch (Exception ex) {
            throw new BadOpaqueTokenException(ex.getMessage(), ex);
        }
    }

    private RequestEntity<?> buildRequest(String token) {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("access_token", token);

        return new RequestEntity<>(body, headers, HttpMethod.POST, URI.create(introspectionUri));
    }
}
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs