Summary

When using ServerOAuth2AuthorizedClientExchangeFilterFunction to add OAuth2 support to a WebClient for calling a protected service, I am unable to configure an http proxy that will be used when calling the token service.

Actual Behavior

When the filter intervenes in the main call to retrieve the bearer token, it uses it's own WebClient instance, which cannot be configured in any way. Thus is can only perform a direct call to the token service, which does not succeed because it needs to be accessed via an http proxy. The main call will correctly use the proxy configured in the main WebClient instance, if the token call were to succeed.

Expected Behavior

I would expect a way to configure the WebClient used by the ServerOAuth2AuthorizedClientExchangeFilterFunction components. This would allow setting up the proxy for that WebClient, as well as any other configuration supported by WebClient.

Version

5.2.1.RELEASE

Sample

Unfortunately the code in question is proprietary and non-public.

Comment From: maartenh

I've created a workaround for the issue by creating a copy of ReactiveOAuth2AuthorizedClientProviderBuilder in my own project and adding a webClient() method to it, that in turn passes this WebClient instance to it's internal builders for the various WebClientReactiveXXXXTokenResponseClients.

I'd like to contribute the modifications back to spring-security in a PR for this issue.

The function could then be used as shown below to create a ServerOAuth2AuthorizedClientExchangeFilterFunction that supports proxies (and other WebClient modifications).

    private ServerOAuth2AuthorizedClientExchangeFilterFunction createOauthFilter(
            final ReactiveClientRegistrationRepository clientRegistrations,
            final ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
            final WebClient.Builder webClientBuilder) {

        final ReactiveOAuth2AuthorizedClientManager authorizedClientManager = createAuthorizedClientManager(
                clientRegistrations,
                authorizedClientRepository,
                webClientBuilder
        );

        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth.setDefaultClientRegistrationId("fedex-oauth2");

        return oauth;
    }

    private ReactiveOAuth2AuthorizedClientManager createAuthorizedClientManager(
            ReactiveClientRegistrationRepository clientRegistrationRepository,
            ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
            WebClient.Builder webClientBuilder) {

        ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider;
        authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                                                                                 .authorizationCode()
                                                                                 .refreshToken()
                                                                                 .clientCredentials()
                                                                                 .password()
                                                                                 .webClient(oauth2WebClient(webClientBuilder))
                                                                                 .build();
        DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    WebClient oauth2WebClient(final WebClient.Builder webClientBuilder) {
        return webClientBuilder.clientConnector(createHttpConnector()).build();
    }

Comment From: jgrandja

@maartenh

ReactiveOAuth2AuthorizedClientProviderBuilder accepts a Consumer for the supported grant methods which gives you access to the builder. With the builder, you can set a custom ReactiveOAuth2AccessTokenResponseClient. The current implementations are WebClientReactiveAuthorizationCodeTokenResponseClient, WebClientReactiveRefreshTokenTokenResponseClient, WebClientReactiveClientCredentialsTokenResponseClient and WebClientReactivePasswordTokenResponseClient. Each of these implementations expose setWebClient().

Here is an example for client_credentials:


WebClientReactiveClientCredentialsTokenResponseClient tokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
tokenResponseClient.setWebClient(...);

ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider =
        ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
                .clientCredentials(builder -> builder.accessTokenResponseClient(tokenResponseClient))
                ....

I'm going to close this issue since this customization is available.

Comment From: maartenh

@jgrandja Thanks for the example, that works quite nicely to solve my issue.