Spring Security 6.3.0-M3 中的令牌交换支持

工程 | 史蒂夫·里森伯格 | 2024年3月19日 | ...

我很高兴地分享,Spring Security 6.3 将支持 OAuth 2.0 令牌交换授权 (RFC 8693),该功能已在最新的里程碑版本 (6.3.0-M3) 中提供预览。此支持允许将令牌交换与 OAuth2 客户端一起使用。同样,服务器端支持也将在 Spring Authorization Server 1.3 中提供,并已在最新的里程碑版本 (1.3.0-M3) 中提供预览。

Spring Security 的 OAuth2 客户端功能使我们能够轻松地向使用 OAuth2 持有者令牌保护的 API 发出受保护资源请求。同样,Spring Security 的 OAuth2 资源服务器功能使我们能够使用 OAuth2 保护 API。让我们来看看如何使用新的支持来构建具有令牌交换功能的 OAuth2 流。

一个例子

让我们假设我们有一个名为 user-service 的资源服务器,提供访问用户信息 API。为了向 user-service 发出请求,客户端必须提供一个访问令牌。我们假设令牌必须具有 user-service 的受众(aud 声明)。这在 Spring Boot 配置属性中可能如下所示:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://my-auth-server.com
          audiences: user-service

现在让我们假设我们想要引入一个新的资源服务器,名为 message-service,并从 user-service 调用它。那么我们假设这个新服务的令牌必须具有 message-service 的受众。显然,我们不能将对 user-service 的请求中的令牌重新用于对 message-service 的请求。但是,我们希望保留原始请求中用户的身份。我们如何实现这一点?

为了获取 message-service 所需的访问令牌,资源服务器 user-service 必须成为一个客户端,并交换一个现有令牌以获取一个保留原始令牌身份(用户)的新令牌。这被称为“模拟”,它正是 OAuth 2.0 令牌交换旨在解决的场景。

将资源服务器配置为客户端

为了启用令牌交换,我们需要将 user-service 配置为既充当资源服务器,又充当可以使用令牌交换的客户端,如下面的 Spring Boot 配置属性所示:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://my-auth-server.com
          audiences: user-service
      client:
        registration:
          my-token-exchange-client:
            provider: my-auth-server
            client-id: token-client
            client-secret: token
            authorization-grant-type: urn:ietf:params:oauth:grant-type:token-exchange
            client-authentication-method: client_secret_basic
            scope:
                - message.read
        provider:
          my-auth-server:
            issuer-uri: https://my-auth-server.com

我们还需要在 Spring Security 中启用新的授权类型,这可以通过发布以下 bean 来实现:

    @Bean
    public OAuth2AuthorizedClientProvider tokenExchange() {
        return new TokenExchangeOAuth2AuthorizedClientProvider();
    }

这是开始使用令牌交换所需的全部。但是,如果我们要请求特定的 audienceresource 值,我们需要在令牌请求中配置附加参数,如下面的示例所示:

    @Bean
    public OAuth2AuthorizedClientProvider tokenExchange() {
        var requestEntityConverter = new TokenExchangeGrantRequestEntityConverter();
        requestEntityConverter.addParametersConverter((grantRequest) -> {
            var parameters = new LinkedMultiValueMap<String, String>();
            parameters.add(OAuth2ParameterNames.AUDIENCE, "message-service");
            parameters.add(OAuth2ParameterNames.RESOURCE, "https://example.com/messages");

            return parameters;
        });

        var accessTokenResponseClient = new DefaultTokenExchangeTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);

        var authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider();
        authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);

        return authorizedClientProvider;
    }

通过此配置,我们可以在一个资源服务器中获取访问令牌,并将其用作 Bearer 令牌,以便向另一个资源服务器发出受保护资源请求。默认情况下,传递给资源服务器的 Authorization 头中的原始持有者令牌将用于获取新的访问令牌。

提示:有关如何获取访问令牌并使用此配置发出受保护资源请求的更多信息,请参阅参考文档中的授权客户端功能

在服务器上启用令牌交换

为了完善整体方案,让我们使用 Spring Authorization Server 构建一个全新的授权服务器应用程序来支持此流程。

使用 Spring InitializrOAuth2 Authorization Server 依赖项,我们可以使用以下 Spring Boot 配置属性来配置一个功能完备的授权服务器:

spring:
  security:
    user:
      name: sally
      password: password
    oauth2:
      authorizationserver:
        client:
          test-client:
            registration:
              client-id: test-client
              client-secret: {noop}secret
              client-authentication-methods:
                - client_secret_basic
              authorization-grant-types:
                - authorization_code
                - refresh_token
              scopes:
                - user.read
          token-client:
            registration:
              client-id: token-client
              client-secret: {noop}token
              client-authentication-methods:
                - client_secret_basic
              authorization-grant-types:
                - urn:ietf:params:oauth:grant-type:token-exchange
              scopes:
                - message.read

与客户端一样,我们可能希望支持令牌交换的特定请求参数,例如 audienceresource,这可以通过发布以下 bean 来实现:

	@Bean
	public OAuth2TokenCustomizer<JwtEncodingContext> accessTokenCustomizer() {
		return (context) -> {
			if (AuthorizationGrantType.TOKEN_EXCHANGE.equals(context.getAuthorizationGrantType())) {
				OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = context.getAuthorizationGrant();
				Set<String> resources = tokenExchangeAuthentication.getResources();
				// TODO: Validate resource value(s) and map to the
				//  appropriate audience value(s) if needed...

				context.getClaims().audience(...);
			}
		};
	}

通过此配置,授权服务器支持带有 OAuth 2.0 令牌请求的可选 resource 参数的令牌交换授权,并且能够颁发令牌,允许资源服务器充当客户端并模拟最终用户。

结论

在这篇博客文章中,我们讨论了令牌交换的“模拟”用例,并探讨了资源服务器(充当客户端)和授权服务器的简单配置。

提示:有关更多示例,包括也受支持的“委托”用例示例,请参阅 RFC 8693附录 A

我希望您和我一样对这项新支持感到兴奋!我鼓励您尝试 Spring Authorization Server 中的示例,其中包括本博客文章的一个工作示例。也请在您自己的项目中尝试 Spring Security 和 Spring Authorization Server 的里程碑版本。我们期待您的反馈!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有