Given the following code, is it possible to call a Client Credentials secured API in an application runner?
@Bean
public ApplicationRunner test(
WebClient.Builder builder,
ClientRegistrationRepository clientRegistrationRepo,
OAuth2AuthorizedClientRepository authorizedClient) {
return args -> {
try {
var oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
clientRegistrationRepo,
authorizedClient);
oauth2.setDefaultClientRegistrationId("test");
var response = builder
.apply(oauth2.oauth2Configuration())
.build()
.get()
.uri("test")
.retrieve()
.bodyToMono(String.class)
.block();
log.info("Response - {}", response);
} catch (Exception e) {
log.error("Failed to call test.", e);
}
};
}
The code fails due to,
java.lang.IllegalArgumentException: request cannot be null
Full stack,
java.lang.IllegalArgumentException: request cannot be null
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.loadAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:47) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.populateDefaultOAuth2AuthorizedClient(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:364) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.lambda$null$2(ServletOAuth2AuthorizedClientExchangeFilterFunction.java:209) ~[spring-security-oauth2-client-5.1.4.RELEASE.jar:5.1.4.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:234) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.attributes(DefaultWebClient.java:153) ~[spring-webflux-5.1.5.RELEASE.jar:5.1.5.RELEASE]
With the failing method looking like,
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(
String clientRegistrationId, Authentication principal, HttpServletRequest request){
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.notNull(request, "request cannot be null");
return (OAuth2AuthorizedClient)this
.getAuthorizedClients(request)
.get(clientRegistrationId);
}
Which makes sense as there is not HttpServletRequest
for it to use, its being called on start-up of the application.
Is there any workarounds other than make my own no-op OAuth2AuthorizedClientRepository
?
//Edit,
This is not a fully reactive stack. It is a Spring Web stack with the WebClient being used with in it.
I am well aware of the ServerOAuth2AuthorizedClientExchangeFilterFunction
which applies to a fully reactive stack and requires ReactiveClientRegistrationRepository
and ReactiveOauth2AuthorizedClient
which are not available due to this being in an application built on top of Servlet stack, not reactive.
Before making a request to the resource server, first check if the token has already expired or is about to expire. If so, request a new token. Finally, make the request to the resource server. Save the token and expiration time in memory, and have a timer which triggers a token refresh some interval before expiry.
Deprecated. See the OAuth 2.0 Migration Guide for Spring Security 5. Rest template that is able to make OAuth2-authenticated REST requests with the credentials of the provided resource.
Since I also stumbled across this problem I'll elaborate a bit on Darren Forsythe's updated answer to make it easier for others to find:
The issue submitted by the OP resulted in an implementation of OAuth2AuthorizedClientManager
that is capable of
operating outside of a HttpServletRequest context, e.g. in a scheduled/background thread and/or in the service-tier
(from the official docs)
Said implementation, the AuthorizedClientServiceOAuth2AuthorizedClientManager
, is passed to the ServletOAuth2AuthorizedClientExchangeFilterFunction
to replace the default one.
In my example this looks something like this:
@Bean
public OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService clientService)
{
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials()
.build();
AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
new AuthorizedClientServiceOAuth2AuthorizedClientManager(
clientRegistrationRepository, clientService);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager)
{
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(
authorizedClientManager);
oauth2.setDefaultClientRegistrationId("keycloak");
return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
}
I ended up asking this to the Spring Security team,
https://github.com/spring-projects/spring-security/issues/6683
Unfortunately if you are on the servlet stack and calling to OAuth2 resources with pure Spring Security 5 APIs in a background thread there isn't a OAuth2AuthorizedClientRepository
available.
Realistically there's two options,
var oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,
new OAuth2AuthorizedClientRepository() {
@Override
public <T extends OAuth2AuthorizedClient> T loadAuthorizedClient(String s,
Authentication authentication, HttpServletRequest httpServletRequest) {
return null;
}
@Override
public void saveAuthorizedClient(OAuth2AuthorizedClient oAuth2AuthorizedClient,
Authentication authentication, HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) {
}
@Override
public void removeAuthorizedClient(String s, Authentication authentication,
HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
}
});
UnAuthenticatedServerOAuth2AuthorizedClientRepository
. UnAuthenticatedServerOAuth2AuthorizedClientRepository GitHub Source which has some basic functionality than a pure no-op. Providing feedback on the GitHub issue might help the Spring Security team to evaluate accepting a PR and maintaining a Servlet version of the UnAuthenticatedServerOAuth2AuthorizedClientRepository
I reached out the the Spring Security Team Spring Security Issue 6683 and on the back of that a Servlet version of the ServerOAuth2AuthorizedClientExchangeFilterFunction
will be added in Spring Security 5.2 for usage on non-http threads.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With