3

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.

issam
  • 53
  • 4
anydoby
  • 408
  • 4
  • 9

1 Answers1

5

switching the ServerOAuth2AuthorizedClientRepository to a ReactiveOAuth2AuthorizedClientService will make the code run:

@Bean
WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations,
                    ReactiveOAuth2AuthorizedClientService authorizedClientService) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrations, authorizedClientService));
    oauth.setDefaultClientRegistrationId("myRegistrationId");
    return WebClient.builder()
                    .filter(oauth)
                    .build();
}

It's recommended to use the ReactiveOAuth2AuthorizedClientManager constructor and pass in AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager.

Source

Bashir
  • 2,057
  • 5
  • 19
  • 44
issam
  • 53
  • 4