【问题标题】:Spring WebClient and shared client credential token for all requests所有请求的 Spring WebClient 和共享客户端凭证令牌
【发布时间】:2021-05-29 20:27:30
【问题描述】:

我尝试使用 Spring WebClient 和 Spring Security OAuth2 Client 在两个应用程序之间进行通信。我需要使用 OAuth2 Client Credentials 授权类型,并对这些应用程序之间的所有请求使用相同的凭据和相同的令牌。在 OAuth2 术语中,资源所有者是应用程序 A 本身,资源服务器是应用程序 B(它是 Keycloak Admin API)。我使用来自 application.yaml 和 servlet 环境的 Spring Boot 自动配置。所以我是这样实现的:

WebClient buildWebClient(ClientRegistrationRepository clientRegistrationRepository,
    OAuth2AuthorizedClientRepository authorizedClientRepository, String clientName) {
  final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
      clientRegistrationRepository, authorizedClientRepository);
  oauth2Client.setDefaultClientRegistrationId(clientName);

  return WebClient.builder()
      .apply(oauth2Client.oauth2Configuration())
      .build();
}

我发现我有一个问题并且无效的令牌(我使用 Keycloak,如果我的 Keycloak 重新启动,那么所有旧令牌都无效)永远不会被删除(你可以在 issue #9477 中看到它)所以我开始调试整个过程ServletOAuth2AuthorizedClientExchangeFilterFunction 中的授权。我发现令牌是应用程序 A 的每个用户保存的。这意味着应用程序 A 的每个用户都有自己的令牌来授权应用程序 B。但这不是我的预期行为我只想在应用程序中使用一个令牌A 用于消耗来自应用程序 B 的资源。

我认为我找到了两个解决方案,但它们都不是最优的。

解决方案 1: 在ServletOAuth2AuthorizedClientExchangeFilterFunction 之后添加另一个默认请求,它将属性中的用户Authentication 替换为应用程序Authentication。问题是它依赖于ServletOAuth2AuthorizedClientExchangeFilterFunction 的内部实现,它使用私有属性AUTHENTICATION_ATTR_NAME = Authentication.class.getName();

  return WebClient.builder()
      .apply(oauth2Client.oauth2Configuration())
      .defaultRequest(spec -> spec.attributes(attr -> 
           attr.put(AUTHENTICATION_ATTR_NAME, new CustomApplicaitonAuthentication())
        )
      .build();

解决方案 2: 使用OAuth2AuthorizedClientRepository 的自定义实现而不是默认的AuthenticatedPrincipalOAuth2AuthorizedClientRepository(由于删除无效令牌的问题我必须这样做),我忽略用户的Authentication 并使用自定义应用程序Authentication

@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String clientRegistrationId, Authentication principal,
                                                                        HttpServletRequest request) {
  return this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, new CustomApplicaitonAuthentication());
}

@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal,
                                    HttpServletRequest request, HttpServletResponse response) {
  this.authorizedClientService.saveAuthorizedClient(authorizedClient, new CustomApplicaitonAuthentication());
}

但我认为我的两种解决方案都不是最优的。还有其他方法可以更直接吗?

【问题讨论】:

    标签: spring spring-security oauth-2.0 spring-security-oauth2


    【解决方案1】:

    我找到了解决方案,因为我需要在没有 servlet 请求(消息传递、调度程序等)的情况下运行它。因此我需要更新它并使用AuthorizedClientServiceOAuth2AuthorizedClientManager 而不是默认的DefaultOAuth2AuthorizedClientManager。然后我意识到它做了我需要的同样的事情。

     @Bean
     WebClient buildWebClient(
          OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager,
          OAuth2AuthorizationFailureHandler oAuth2AuthorizationFailureHandler,
          String clientName) {
    
        final ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(
            oAuth2AuthorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId(clientName);
        oauth2Client.setAuthorizationFailureHandler(oAuth2AuthorizationFailureHandler);
    
        return WebClient.builder()
            .apply(oauth2Client.oauth2Configuration())
            .build();
      }
    
    
      @Bean
      public OAuth2AuthorizedClientManager authorizedClientManager
          (ClientRegistrationRepository clients, OAuth2AuthorizedClientService service, OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
        AuthorizedClientServiceOAuth2AuthorizedClientManager manager =
            new AuthorizedClientServiceOAuth2AuthorizedClientManager(clients, service);
        manager.setAuthorizationFailureHandler(authorizationFailureHandler);
        return manager;
      }
    
      @Bean
      public OAuth2AuthorizationFailureHandler resourceServerAuthorizationFailureHandler(
          OAuth2AuthorizedClientService authorizedClientService) {
        return new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
            (clientRegistrationId, principal, attributes) -> 
              authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName()));
      }
    
    

    我们必须添加自定义OAuth2AuthorizationFailureHandler,因为如果您不使用此构造函数ServletOAuth2AuthorizedClientExchangeFilterFunction(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) 而使用自定义OAuth2AuthorizedClientManager,则它未配置,您必须手动进行。

    【讨论】:

    • 一个简短的问题,我真的很想得到答案。 ServletOAuth2AuthorizedClientExchangeFilterFunction 用于 MVC 上下文,或者说不同:如果我在 MVC 上下文中使用 WebClient。我使用 ServerOAuth2AuthorizedClientExchangeFilterFunction 作为 webflux 上下文?
    猜你喜欢
    • 2019-04-10
    • 2017-10-27
    • 2017-08-22
    • 2020-02-23
    • 1970-01-01
    • 2019-01-08
    • 2016-12-17
    • 1970-01-01
    • 2022-01-26
    相关资源
    最近更新 更多