如何使用 Spring Cloud Security 实现 OAuth2“令牌交换”

Posted

技术标签:

【中文标题】如何使用 Spring Cloud Security 实现 OAuth2“令牌交换”【英文标题】:How to implement OAuth2 "Token Exchange" with Spring Cloud Security 【发布时间】:2016-04-26 15:05:11 【问题描述】:

我想知道是否有人有一个例子来看看如何使用 Spring Cloud Security(使用 OAuth2)实现“令牌交换”技术。

目前我已经在微服务环境中实现了“令牌中继”技术,使用 ZuulProxy 来“中继”OAuth2 令牌并实现 SSO。这很好,但意味着每个微服务都使用相同的 clientId(在 ZuulProxy 设置中指定,因为 ZuulProxy 仅使用 authentication_code 授权类型和提供的 clientId 中继令牌)。 但是,对于内部微服务调用,我想“交换”令牌。这意味着在某些情况下,ZuulProxy 中继的令牌不是我需要用来验证/授权微服务 A 作为微服务 B 的客户端的令牌。

Spring Cloud 参考文档目前说:“基于 Spring Boot 和 Spring Security OAuth2,我们可以快速创建实现常见模式的系统,例如单点登录、令牌中继和令牌交换。” (http://cloud.spring.io/spring-cloud-security/spring-cloud-security.html)

我想参考文档中的“令牌交换”是指 OAuth2 扩展的实现,在本规范中进行了解释,这基本上是我需要的: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-token-exchange-03

正如我所说,我了解如何使用 SSO 和令牌中继,但我无法在参考文档中看到有关如何实现“令牌交换”的进一步说明。我也找不到实现示例。

有人知道我在哪里可以找到更多信息或示例吗?

非常感谢!

【问题讨论】:

同意。在所有微服务中重新使用 client_id 似乎是错误的。最糟糕的部分(在我看来)是它阻止了微服务被其他客户端使用。假设您有另一个具有自己的 client_id 的 sso 客户端......那么您不能使用任何现有的微服务?希望这得到解决。似乎已经完成了一些代币交换工作,但尚未完成/合并github.com/spring-projects/spring-security-oauth/pull/957 乍一看,我同意你的看法。但从您的客户的角度来看,他们将您的 API 网关视为一个奇点。从他们的角度来看,只有一个 API。您背后拥有多项服务这一事实是一个实施细节,不一定是您想向客户提供的服务。你决定将一个服务分成两个服务会发生什么?您是否必须让每个人重新发行授予新资源 ID 的令牌? 【参考方案1】:

我很好奇为什么您需要“交换”令牌以从微服务 A 向微服务 B 进行调用,以及为什么中继还不够?您通过为服务间请求交换令牌来尝试实现什么?

我们的设置与Nordic APIs entry 中描述的非常相似。简短的版本是外部调用者使用不透明的令牌,但是一旦请求通过我们的网关,每个微服务都会获得相同令牌的 JWT 表示。我们必须实现一个自定义端点来执行对 JWT 的不透明交换。当服务需要相互交互时,当 A 需要调用 B 时,我们不交换令牌,我们只是中继令牌。 RestTemplate 或 Feign 客户端都会自动将令牌从 A 转发到 B。因此,上下文不会丢失。

现在,如果我们想控制访问,JWT 可以指定一组受众值,或者我们可以通过范围强制访问。我们实际上是根据用例将两者结合起来。

交换令牌并不是一项廉价的操作,事实上它在规模上相当昂贵,并且应该真正考虑为什么需要进行令牌交换以进行服务内通信。如果您的每个 API 请求都会导致服务 A 调用服务 B,并且您必须进行令牌交换,那么您将确保您的授权服务可以处理这种类型的工作负载。最后,IETF 令牌交换仍处于草案状态,并且在其演变过程中发生了相当大的变化,因此在规范接近最终确定之前,我不会对实施建议的方式抱有太大期望。

【讨论】:

【参考方案2】:

我认为这是你可以尝试的。

在我的项目中,我们还使用 OAuth2、Eureka、Ribbon 让微服务相互通信。为了将 Ribbon 与 OAuth2 一起使用,我们采用的方法有点不同。

首先我们保持 restTemplate 不变。

  @LoadBalanced
  @Bean
  public RestTemplate restTemplate() 

但是,我们创建了 FeignClientIntercepter 实现 RequestIntercepter,它在通过 restTemplate 发出请求时为 OAuth 设置授权令牌。

  @Component
  public class FeignClientInterceptor implements RequestInterceptor 

    private static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String BEARER_TOKEN_TYPE = "Jwt";

    @Override
    public void apply(RequestTemplate template) 
      SecurityContext securityContext = SecurityContextHolder.getContext();
      Authentication authentication = securityContext.getAuthentication();

      if (authentication != null && authentication
          .getDetails() instanceof OAuth2AuthenticationDetails) 
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication
          .getDetails();
        template.header(AUTHORIZATION_HEADER,
            String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
      
    
  

【讨论】:

以上是关于如何使用 Spring Cloud Security 实现 OAuth2“令牌交换”的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 spring-cloud-netflix 和 feign 编写集成测试

如何在本地使用 spring-cloud-starter-aws 运行应用程序?

如何在 Spring-Cloud 中将 ConsulDiscoveryClient 与 Zuul 和 Sidecar 一起使用

如何配置 spring-cloud-gateway 以使用 sleuth 记录请求/响应正文

spring cloud config如何使用本地属性覆盖远程属性

spring cloud config:如何使用多个配置