I have been struggling for 2 days already to get Spring WebClient authenticate my service against a (presumably) OAuth2 endpoint. The flow is simple, I need to get a token from some endpoint and set it to authorization header. I thought springboot can do it transparently.
Here's a piece of code that configures my web client:
private static final String SALES_FORCE = "sf";
@Bean
public WebClient salesforceClient(@Value("${salesforce.url}") String salesforceUrl,
ReactiveClientRegistrationRepository clientRegistrationRepository, ReactiveOAuth2AuthorizedClientService clientService) {
var clientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, clientService);
var oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
oauth2Client.setDefaultClientRegistrationId(SALES_FORCE);
return WebClient.builder()
.baseUrl(salesforceUrl)
.filter(oauth2Client)
.build();
}
and here is my configuration for transparent authentication:
spring.security.oauth2.client.registration.sf.client-id=<secret_id>
spring.security.oauth2.client.registration.sf.client-secret=<secret_key>
spring.security.oauth2.client.registration.sf.authorization-grant-type=refresh_token
spring.security.oauth2.client.registration.sf.user-info-authentication-method=form
spring.security.oauth2.client.provider.sf.token-uri=https://auth.exacttargetapis.com/v1/requestToken
It took me a while to figure out the which oauth2 classes to use, but finally the application started. I replaced token-uri with a WireMock on localhost only to discover that Spring does not even try to retrieve the token. I then replaced my destination server with a WireMock and returned a 401 assuming this would trigger token retrieval or refresh, but still Spring does not get the token.
I am already beginning to consider manual management of this token retrieval / expiration as Spring seems to be too much magic for me.
What am I doing wrong?
PS.
As per javadoc suggestion from ServerOAuth2AuthorizedClientExchangeFilterFunction I also tried the second constructor which (presumably) also manages token expiration automatically.
private static final String SALES_FORCE = "sf";
@Bean
public WebClient salesforceClient(@Value("${salesforce.url}") String salesforceUrl,
ReactiveClientRegistrationRepository clientRegistrationRepository
, ServerOAuth2AuthorizedClientRepository clientRepository) {
var oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, clientRepository);
oauth2Client.setDefaultClientRegistrationId(SALES_FORCE);
return WebClient.builder()
.baseUrl(salesforceUrl)
.filter(oauth2Client)
.build();
}
But here my request immediately fails with
java.lang.IllegalArgumentException: serverWebExchange cannot be null
at org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager.lambda$authorize$4(DefaultReactiveOAuth2AuthorizedClientManager.java:131)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Request to POST null [DefaultWebClient]
Stack trace:
at ....
All in all I feel that springboot WebClient is way too complex to configure and use compared to manually managing security tokens.