We migrated to Spring Security from the Zalando Tokens library which was able to fetch the JWT Tokens for the OAuth2 client_credentials flow in the background with an own thread.
With the Spring Security and using ClientCredentialsOAuth2AuthorizedClientProvider we see that the JWT token is refreshed once a new request with the WebClient is made and either the existing token expired or there is no token at all.
We see a small peak for our endpoint after each expiration period (as the JWT gets refreshed for all our different WebClient beans - e.g. we use 4 WebClient beans each having its own scope and targeting a different system) and wanted to ask if such a feature is already available.
Comment From: jgrandja
@rieckpil AuthorizedClientServiceOAuth2AuthorizedClientManager was introduced in 5.2, which can be used with ClientCredentialsOAuth2AuthorizedClientProvider when operating outside of a request context, eg. background thread. The reactive implementation is coming in 5.3 via #7569.
Does this answer your question?
Comment From: rieckpil
thanks for the quick response @jgrandja. As we currently just used the reactive alternatives, I'll try to use the AuthorizedClientServiceOAuth2AuthorizedClientManager you mentioned.
Comment From: rieckpil
If we manage to use the AuthorizedClientServiceOAuth2AuthorizedClientManager how can we use it to refresh our JWT Token in the background? My main intent is to have a way to refresh the JWT tokens for our different ClientRegistrations NOT ONLY when an actual WebClient makes a request but also manually like e.g. with @Scheduled
Comment From: jgrandja
@rieckpil
My main intent is to have a way to refresh the JWT tokens for our different
ClientRegistrationsNOT ONLY when an actualWebClientmakes a request but also manually like e.g. with@Scheduled
Given the following configuration:
@Bean
WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.filter(oauth)
.build();
}
@Bean
ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager =
new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
You could simply call authorize() on the ReactiveOAuth2AuthorizedClientManager @Bean in your @Scheduled component on a recurring interval. This way, the access token will be renewed (when expired) in the @Scheduled component instead of when the WebClient initiates a request. Makes sense?
Comment From: rieckpil
thanks for the example @jgrandja. Now I get it. With the setup above and with Spring Security 5.3 (including the reactive version AuthorizedClientServiceOAuth2AuthorizedClientManager) my @Scheduled method would execute e.g. the following for each of the registrations:
authorizedClientManager.authorize(OAuth2AuthorizeRequest.withClientRegistrationId("my-id").build());?
Comment From: jgrandja
@rieckpil Yes, that is correct. You can achieve the background token renewals using @Scheduled and calls to ReactiveAuthorizedClientManager.authorize() passing in the clientRegistrationId to check for token renewal.
I'm going to close this issue as it seems that I've answered your question.
Comment From: rieckpil
thank you @jgrandja
Comment From: DanielSvanstrom
This solution is one huge step towards what we are trying to achieve @jgrandja. We have a very similar situation but we would also like to make sure that we never do the authorization calls as part of the transaction but only in a separate thread. From what I gather when a client call realizes there is not authorization it also uses the same logic with determining if a token should be renewed. If that is the case there is no way we can guarantee that the scheduled call is made before a potential call from a client realizing the token should be renewed?
How would we go about solving this scenario? Using a different AuthorizedClientManager with a larger clockSkew setting? Something else?
Not sure this is the best place to ask, but this thread was by far the best one describing what we are trying to achieve. Do tell me if I should go elsewhere.
Comment From: jgrandja
@DanielSvanstrom
we would also like to make sure that we never do the authorization calls as part of the transaction but only in a separate thread
This is application-specific behaviour so it would need to be implemented in the application itself. Our focus is to provide protocol (spec) implementations only within the framework.
The best place to ask this question is on Stack Overflow. Do a search first as it might have already been asked and answered.
Comment From: DanielSvanstrom
Thanks for your prompt reply and time @jgrandja.
I have tried stackoverflow already and I have yet to see an answer that covers what I am trying to achieve. I have asked on stackoverflow about it already and still hope for help. After looking at this thread there are still some things we have not yet been able to get answered about spring security that, even if I don't want to do it, moving away from spring security is the only reasonable option left.
I had no intention to pollute this thread, I simply was stuck and didn't know where else to try. I won't continue this discussion here.
Comment From: jgrandja
@DanielSvanstrom Please reply with link to SO question and I'll see what option(s) I can think of for your use case.
Comment From: DanielSvanstrom
https://stackoverflow.com/questions/66658176/periodically-refresh-access-token-in-a-separate-thread @jgrandja Any help I can get is appreciated.
Comment From: jgrandja
@DanielSvanstrom I posted a solution on SO.