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 :
- https://stackoverflow.com/a/64451287
- https://stackoverflow.com/a/68625592
- https://stackoverflow.com/a/50759253 (older one)
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 !