Spring webclient : retry with a new token

Viewed 575

Using Spring Boot 2.5, I am trying to configure a webClient at the builder level that, when it gets a 401, will remove the current token and then try again to call the resource (so the webclient, realizing there's no token anymore, will fetch a new one before actually calling the resource).

This is what I have so far :

  @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();
  }

I can see that when using this webClient, RemoveAuthorizedClientOAuth2AuthorizationFailureHandler comes into picture and removeAuthorizedClient is called. If I make a second call through this webClient, it automatically fetchs a new token before making the actual call.

So now, I want to configure the retry part, and I was thinking an additional filter would do the trick :

  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);
          }
        });
  }

and now the webClient is built with it :

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

From what I can observe, the retry happens before removeAuthorizedClient is called. so the exact same call is retried, with the same token - which obviously fails again.

I've seen a couple of similar questions, but the solutions feel "hack-ish" as it usually requires manipulating the headers myself in one way or another :

Surely, there must be a better "Spring" way to achieve this, right ? Is there a way for removeAuthorizedClient to be called before the retry ?

Thanks !

0 Answers
Related