This is a Spring Boot application where as part of a request an external service is contacted and that service is accessed and authorization is done by passing an access token. Spring security client 5.4.2 is used for all of these steps (and the web client is a Spring Webflux webclient).
The problem: The authentication is done as part of the actual request. The application will do a lot of requests to the external service and all those requests are using the same client id and secret (and thus they share the access token). Unfortunately retrieving a new access token is a heavy process (time-wise) and having the access token retrieval process as part of the request (even if it is the first one) will cause noticeable delays, which causes issues for the rest of the transaction flow. Ideally the access token should be retrieved and refreshed (ahead of expiry time) as a separate thread and the requests should use that access token.
Setting up the beans for the authentication and web client:
@Bean
public WebClient oAuthWebClient(ExchangeFilterFunction ourApiOauthFilter, ClientHttpConnector clientHttpConnector) {
return WebClient.builder()
.clientConnector(clientHttpConnector)
.filter(ourApiOauthFilter)
.build();
}
@Bean
public ReactiveClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryReactiveClientRegistrationRepository(ClientRegistration
.withRegistrationId("our-reg-id")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.clientId(clientId)
.clientSecret(clientSecret)
.tokenUri(oauth2Uri)
.scope(List.of("scope1", "scope2"))
.build());
}
@Bean
public ExchangeFilterFunction ourApiOauthFilter(ReactiveClientRegistrationRepository clientRegistrationRepo) {
InMemoryReactiveOAuth2AuthorizedClientService authorizedClientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepo);
ServerOAuth2AuthorizedClientExchangeFilterFunction filter = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepo, authorizedClientService));
filter.setDefaultClientRegistrationId("our-reg-id");
return filter;
}
From this I have been trying to get a configuration added that could have the token refreshed automatically well before it expires to ensure that it is always there but not been able to get further than this. The refresh is the biggest problem as we could always make a dummy call to the service before we let any transactions flow through the application to ensure we have a access token the first time.
Question: How can I get the application to refresh this access token periodically and independent from the actual transactions and at the same time make sure no transactions will need to authenticate?
EDIT: After originally asking this question I ran across this issue, where in the thread there were some good insights on how to get one step further. However I am still left with the final obstacle and that is to make sure we get all of our authentication done in the timed thread and not as part of the "transaction threads". From what I gather the token expire occurs at the same time in the timed thread and transaction thread and as such there is no guarantee the timed thread would get to it and complete the token request before any transaction deals with the same thread.