Spring webclient:使用新令牌重试

Vin*_*t F 8 spring-boot spring-webflux

使用 Spring Boot 2.5,我尝试在构建器级别配置一个 webClient,当它收到 401 时,将删除当前令牌,然后再次尝试调用资源(因此,webclient 意识到不再有令牌,将获取在实际调用资源之前创建一个新资源)。

这是我到目前为止所拥有的:

  @Bean
  WebClient servletWebClient(ClientRegistrationRepository  clientRegistrations,
                      OAuth2AuthorizedClientRepository authorizedClients) {

    //this constructor  will configure internally a RemoveAuthorizedClientOAuth2AuthorizationFailureHandler,
    // and onAuthorizationFailure will be called on it when we get a 401
    var oauth = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);

    oauth.setDefaultClientRegistrationId("keycloak");

    return WebClient.builder()
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .apply(oauth.oauth2Configuration())
        .build();
  }
Run Code Online (Sandbox Code Playgroud)

我可以看到,当使用这个 webClient 时,RemoveAuthorizedClientOAuth2AuthorizationFailureHandler出现在图片中removeAuthorizedClient并被调用。如果我通过此 webClient 进行第二次调用,它会在进行实际调用之前自动获取新令牌。

所以现在,我想配置重试部分,我想一个额外的过滤器可以做到这一点:

  ExchangeFilterFunction retryOn401Function() {

    return (request, next) -> next.exchange(request)
        .flatMap((Function<ClientResponse, Mono<ClientResponse>>) clientResponse -> {
          if (clientResponse.statusCode().value() == 401) {
            log.warn("got an unauthorized status when calling service - will retry once");
            return next.exchange(request).retry(1L);
          } else {
            return Mono.just(clientResponse);
          }
        });
  }
Run Code Online (Sandbox Code Playgroud)

现在 webClient 是用它构建的:

    return WebClient.builder()
        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
        .apply(oauth.oauth2Configuration())
         // filter added
        .filter(retryOn401Function())
        .build();
Run Code Online (Sandbox Code Playgroud)

据我观察,重试发生在调用之前。 removeAuthorizedClient因此,使用相同的令牌重试完全相同的调用 - 这显然再次失败。

我见过几个类似的问题,但解决方案感觉“hack-ish”,因为它通常需要我自己以一种或另一种方式操作标题:

当然,一定有更好的“Spring”方式来实现这一点,对吧?有没有办法removeAuthorizedClient在重试之前调用?

谢谢 !

小智 0

首先,您应该使用过滤器进行重试,然后使用过滤器来删除令牌。

    return WebClient.builder()
    .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
    .filter(retryOn401Filter())
    .filter(oauth)
    .build();
Run Code Online (Sandbox Code Playgroud)

抱歉忘记了令牌删除逻辑,它可能如下所示:

private ServerOAuth2AuthorizedClientExchangeFilterFunction getAuthorizationFilter(
        CustomClientCredentialsReactiveOAuth2AuthorizedClientProvider provider,
        Properties properties
) {
    ReactiveClientRegistrationRepository clientRegistration = clientRegistration(properties);
    ReactiveOAuth2AuthorizedClientService authorizedClientService = authorizedClient(clientRegistration);
    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager manager =
            new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistration, authorizedClientService);
    manager.setAuthorizedClientProvider(provider);
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(manager);

    //removes access token in case of 401/403 http error codes
    RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler removeAuthorizedClientReactiveOAuth2AuthorizationFailureHandler =
            new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
                    (clientRegistrationId, principal, attributes) ->
                            authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName())
            );
    oauth.setAuthorizationFailureHandler(removeAuthorizedClientReactiveOAuth2AuthorizationFailureHandler);
    oauth.setDefaultOAuth2AuthorizedClient(true);
    oauth.setDefaultClientRegistrationId("pyrus");
    return oauth;
}
Run Code Online (Sandbox Code Playgroud)