Spring Boot 授权服务器 + Google OAuth2/OpenId Connect 应该使用 access_token 还是 id_token?

发布于 2025-01-09 21:44:16 字数 2632 浏览 1 评论 0 原文

我对于是否应该通过 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、客户端密钥)
  • JavaScript SPA 应用程序(例如 React)将用户登录到 Google 并向 Spring Boot 资源服务器发出请求以获取安全数据。这些请求包括登录用户的授权标头(带有从 Google 获取的不记名令牌)。
  • 出于开发目的,我还使用 Postman 向 Spring Boot 资源服务器发出请求,

我可以轻松配置 Postman 以从 Google 获取令牌。来自 Google 的此令牌响应包括 access_tokenid_tokenscopeexpries_intoken_type 的值代码>.

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

这是我能够成功访问安全的唯一方法>@Controllers 是通过使用 id_token 作为 Authorization 标头中的 Bearer 来实现的。

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

一些其他相关信息:

  • id_token 的值是 JWT 令牌。 access_token 的值不是 JWT 令牌。我知道这一点是因为我可以解码 jwt.io 上的 id_token 但它无法解码access_token 的值。此外,当我将 access_token 作为 Authorization 标头中的 Bearer 发送时,Spring Boot 资源服务器会失败并出现以下错误:

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

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

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

总之:

  • 我可以使用从 Google 获取的 id_token 值来访问我的 Spring Boot 资源服务器。 access_token 的值不是 JWT,Spring Boot 无法解析。
  • 是我的理解有问题,还是我的配置有问题? Google 的 OpenId Connect 在 access_token 的工作方式方面是否有不同的行为?

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

I'm a bit confused regarding whether I should be accessing my Spring Boot Resource Server via an access_token or an id_token.

First, let me quickly explain my setup:

  • Spring Boot app as an OAuth 2.0 Resource Server. This is configured as described in the Spring docs: Minimal Configuration for JWTs This app provides secured @Controllers that will provide data for a JavaScript SPA (eg. React)
  • Google's OAuth 2.0 AP / OpenID Connect already configured (Credentials, Client Id, Client Secret)
  • A JavaScript SPA app (eg. React) that logs the user into Google and makes requests to the Spring Boot Resource Server for secured data. These requests include the Authorization header (with Bearer token obtained from Google) for the logged in user.
  • For development purposes, I'm also using Postman to make requests to the Spring Boot Resource Server

I can easily configure Postman to get a token from Google. This token response from Google includes values for access_token, id_token, scope, expries_in and token_type.

However, my requests to the Resource Server are denied when Postman tries to use the value from retrieved token's access_token field as the Bearer in the Authorization header

The only way I'm able to successfully access the secured @Controllers is by using the id_token as the Bearer in the Authorization header.

Is it expected that I should use the id_token as the Bearer in the Authorization header? Or is it expected that I should use the access_token?

Some additional relevant info:

  • The value of the id_token is a JWT token. The value of the access_token is not a JWT token. I know this because I can decode the id_token on jwt.io but it is unable to decode the value of the access_token. Further, the Spring Boot Resource Server fails with the following when I send the access_token as the Bearer in the Authorization header:

An error occurred while attempting to decode the Jwt: Invalid unsecured/JWS/JWE header: Invalid JSON: Unexpected token ɭ� at position 2.

You should not use an identity token to authorize access to an API.
To access an API, you should be using OAuth’s access tokens, which are intended only for the protected resource (API) and come with scoping built-in.

  • Looking at at the spring-security-samples for using OAuth2 Resource Server, I see the value of there hard-coded access_token (for testing purposes) is indeed a valid JWT. As opposed to the access_token returned from Google which is not a JWT.

In summary:

  • I can access my Spring Boot Resource Server using the value of the id_token obtained from Google. The value of the access_token is not a JWT and fails to parse by Spring Boot.
  • Is there something wrong with my understanding, my configuration or what? Does Google's OpenId Connect behave differently regarding how the access_token works?

Happy to clarify or add more info if needed. Thanks for your consideration and your patience!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉态萌生 2025-01-16 21:44:16

在我看来,您提到的博客文章是正确的,并且我相信 OpenID Connect 1.0 规范并不打算将 id_token 用于访问目的。

和您一样,我预计使用 Google 作为授权服务器可以开箱即用,因为 Spring Security 与 Google 一起作为公共 OAuth2 提供者来提供社交登录。然而,事实并非如此,而且我相信这并不是真正的意图,因为 Google 并不是真正的您的授权服务器。例如,我不相信您可以将 Google 配置为使用特定于域的应用程序的范围/权限/权限。这与 Okta 之类的东西不同,Okta 中有很多用于在您自己的租户中配置事物的选项。

我实际上建议查看 Spring 授权服务器,并将 Google 配置为联合服务器身份提供者。我目前正在为此编写一个示例,它将在下周左右发布(更新:现在包含在 官方示例)。

话虽如此,如果您仍然对使用 Google 访问令牌对资源服务器进行身份验证的简单用例感兴趣,则需要提供自己的 不透明令牌使用 Google 的 的内省器 tokeninfo 端点。它与 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

The blog post you mentioned is correct in my view, and I believe the OpenID Connect 1.0 spec does not intend for an id_token to be used for access purposes.

Like you, I expected that using Google as an Authorization Server would work out of the box, because Spring Security works with Google as a common OAuth2 provider for providing social login. However, this is not the case, and I believe it is not really intended, because Google is not really your authorization server. For example, I don't believe you can configure Google to work with scopes/permissions/authorities of your domain-specific application. This is different from something like Okta, where there are many options for configuring things in your own tenant.

I would actually recommend checking out Spring Authorization Server, and configuring Google as a federated identity provider. I'm working on a sample for this currently and it will be published within the next week or so (update: now contained in the official sample).

Having said that, if you're still interested in a simple use case where Google access tokens are used for authenticating with your resource server, you would need to provide your own opaque token introspector that uses Google's tokeninfo endpoint. It doesn't match what Spring Security expects, so it's a bit involved.

@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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文