首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >从WebClient OAuth2 refresh_token中安装Spring refresh_token过滤器

从WebClient OAuth2 refresh_token中安装Spring refresh_token过滤器
EN

Stack Overflow用户
提问于 2022-09-15 15:19:54
回答 1查看 212关注 0票数 1

我必须使用一个外部API,它使用OAuth2作为安全性。它们不支持授予类型"client_credentials",而是提供了一个长期存在的refresh_token,我们可以将其注入到Spring应用程序中,而不需要它过期。

但是,我找不到关于如何为OAuth2设置WebClient过滤器的任何信息--使用给定的refresh_token获取access_token。

下面是一些代码,以获得一个起点,并说明我在哪里尝试过。

代码语言:javascript
复制
    @Value("application.external-api.refresh_token")
    private String refresh_token;

    @Bean
    public WebClient externalApiWebClient(
            ReactiveClientRegistrationRepository clientRegistrations,
            ServerOAuth2AuthorizedClientRepository authorizedClients
    ) {
        ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
                new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
        oauth.setDefaultClientRegistrationId("external-api");

        // TODO: Somehow get the refresh_token into the OAuth filter

        return WebClient.builder()
                .baseUrl("https://api.external-api.com")
                .filter(oauth)
                .build();
    }
代码语言:javascript
复制
spring:
  security:
    oauth2:
      client:
        registration:
          external-api:
            # client-secret: <refresh_token>
            authorization-grant-type: refresh_token

        provider:
          external-api:
            token-uri: https://api.external-api.com/oauth2/token

有人需要做一些类似的事情并找到解决这个问题的方法吗?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-09-15 21:53:35

是的,我做过这样的事。我所处的环境是(试图)与Google智能设备管理生态系统集成。我不确定我是否推荐它,但我至少成功地使用了刷新令牌连接到API。

我可以想出两种方法来实现这一点:

  1. 自己实现ReactiveOAuth2AuthorizedClientService
  2. 在启动时使用内存中的实现并加载refreshToken

这两种方法都是相似的,因为无论在哪种情况下,我们都没有accessToken,必须给框架一个过期的令牌,所以它知道在第一次使用时加载一个新的令牌。

我选择了第二种选择,尽管事后看来,第一种选择可能会更好。

下面是一个类,它将所有概念联系在一起,并允许您为基于请求的访问或不响应用户请求的后台服务(例如计划的批处理作业)创建一个WebClient

代码语言:javascript
复制
@Component
public class WebClientFactory {

    private final ReactiveClientRegistrationRepository clientRegistrationRepository;

    private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;

    private final ReactiveOAuth2AuthorizedClientService authorizedClientService;

    private final ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider;

    private final String baseUrl;

    public WebClientFactory(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
            ReactiveOAuth2AuthorizedClientService authorizedClientService,
            ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider,
            @Value("${my.base-url}") String baseUrl) {
        this.clientRegistrationRepository = clientRegistrationRepository;
        this.authorizedClientRepository = authorizedClientRepository;
        this.authorizedClientService = authorizedClientService;
        this.authorizedClientProvider = authorizedClientProvider;
        this.baseUrl = baseUrl;
    }

    public Mono<Void> saveRefreshToken(String registrationId, String principalName, String token) {
        var now = Instant.now();
        var accessToken = new OAuth2AccessToken(
            OAuth2AccessToken.TokenType.BEARER, "none", now.minusSeconds(2), now.minusSeconds(1));
        var refreshToken = new OAuth2RefreshToken(token, now);
        var principal = new BearerTokenAuthenticationToken(principalName);
        principal.setAuthenticated(true);

        return this.clientRegistrationRepository.findByRegistrationId(registrationId)
            .switchIfEmpty(Mono.error(new InternalAuthenticationServiceException("Unable to find registrationId " + registrationId)))
            .map((clientRegistration) -> new OAuth2AuthorizedClient(clientRegistration, principalName, accessToken, refreshToken))
            .flatMap((authorizedClient) -> this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal));
    }

    public WebClient createRequestClient() {
        var authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
            this.clientRegistrationRepository, this.authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);

        return createWebClient(authorizedClientManager);
    }

    public WebClient createBackgroundClient() {
        var authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            this.clientRegistrationRepository, this.authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(this.authorizedClientProvider);

        return createWebClient(authorizedClientManager);
    }

    private WebClient createWebClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
        var oauth2 = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        return WebClient.builder()
            .filter(oauth2)
            .baseUrl(this.baseUrl)
            .build();
    }

}

注意:为了简单起见,我正在从spring-security-oauth2-resource-server重用BearerTokenAuthenticationToken,因为需要某种类型的主体来唯一地标识刷新令牌。

Spring将为您提供大部分注入bean(默认为内存中的),但您需要提供ReactiveOAuth2AuthorizedClientProvider

代码语言:javascript
复制
    @Bean
    public ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider() {
        return ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
            .refreshToken()
            .build();
    }

如果在后台服务中使用BearerTokenAuthenticationToken,则需要自己通过ReactiveSecurityContextHolder设置与principal相同的WebClient

有关更多信息,请参见文档

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73733680

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档