I'm using 5.7.2 via Spring Boot security starters.

ReactiveOAuth2AuthorizedClientManager.authorize is called twice for each WebClient request when ServerOAuth2AuthorizedClientExchangeFilterFunction is used and configured to find client registration id as request attribute.

The WebClient code looks like this:

WebClient
    .builder()
    .baseUrl("<url>")
    .filter(
        ServerOAuth2AuthorizedClientExchangeFilterFunction(
            clientRegistrationRepository,
            authorizedClientRepository,
        ).also {
            it.setDefaultClientRegistrationId("<registration id>")
        },
    )
    .build()
    .get()
    .uri("<relative uri>")
    .retrieve()
    .awaitBody()

or

WebClient
    .builder()
    .baseUrl("<url>")
    .filter(
        ServerOAuth2AuthorizedClientExchangeFilterFunction(
            clientRegistrationRepository,
            authorizedClientRepository,
        ),
    )
    .build()
    .get()
    .uri("<relative uri>")
    .attributes(clientRegistrationId("<registration id>"))
    .retrieve()
    .awaitBody()

Whenever a WebClient request is made ServerOAuth2AuthorizedClientExchangeFilterFunction.filter is called only once, but internally ReactiveOAuth2AuthorizedClientManager.authorize is called twice for each request.

Expected behavior ReactiveOAuth2AuthorizedClientManager.authorize is called only once.

This double call doesn't happen when an OAuth2AuthorizedClient is registered using .attributes(oauth2AuthorizedClient(client)) (which is not an option in my use case, I don't have the client at hand to set it like this).

I believe the reason is this chain: https://github.com/spring-projects/spring-security/blob/5.7.2/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/reactive/function/client/ServerOAuth2AuthorizedClientExchangeFilterFunction.java#L446-L452 as reauthorizeRequest is always called, even for a just-authorized client. A potential fix could be to do this: 1. If only a client id is available in the attributes, call authorizeRequest(request) and let it authorize the client, and not reauthorize afterwards (as it is not necessary, the client has just been authorized). 2. If a client is available in the attributes, only call reauthorizeRequest(request, authorizedClient) to make sure the authorization is up-to-date.

Comment From: jgrandja

@wujek-srujek

I believe the reason is this chain: ... as reauthorizeRequest is always called, even for a just-authorized client

Are you able to put together a test that demonstrates the double call? If the double call is an issue, are you able to provide a PR with the proposed fix?

Comment From: wujek-srujek

I attach a sample, run it with ./mvnw spring-boot:run.

You need to edit src/main/resources/application.yaml and set the correct settings for the client application to be able to fetch tokens. I used a test Oauth0 account but anything will work. (If you do use Oauth0, they require either an audience parameter in the token request, or a default audience - go to Settings -> General -> API Authorization Settings -> fill Default Audience with the value from Applications -> APIs, right to your test API there should be its audience.)

When the application runs, go to http://localhost:8080/token to trigger the demo.

To notice the double authorize calls you will need to set a few breakpoints: 1. DefaultReactiveOAuth2AuthorizedClientManager.authorize - this will be called twice for each /token request you initiate. 1. ServerOAuth2AuthorizedClientExchangeFilterFunction.authorizedClient - the method with the 'authorize -> reauthorize' chain that eventually calls DefaultReactiveOAuth2AuthorizedClientManager.authorize twice in a row. 1. ServerOAuth2AuthorizedClientExchangeFilterFunction.authorizeRequest. 1. ServerOAuth2AuthorizedClientExchangeFilterFunction.reauthorizeRequest.

I won't be able to provide a PR with a fix, sorry.

spring_security_11615.zip

Comment From: jgrandja

@wujek-srujek

Whenever a WebClient request is made ServerOAuth2AuthorizedClientExchangeFilterFunction.filter() is called only once, but internally ReactiveOAuth2AuthorizedClientManager.authorize is called twice for each request.

This is the expected behaviour. The 2nd call is happening right after ServerOAuth2AuthorizedClientExchangeFilterFunction.reauthorizeRequest(). The purpose of calling ReactiveOAuth2AuthorizedClientManager.authorize() with a provided OAuth2AuthorizeRequest.authorizedClient is to determine if the client needs to be re-authorized in the cases when the access token has expired. I realize this 2nd call is redundant right after the client is authorized the first time but adding logic to avoid this 2nd call in this one instance complicates the internal logic. Furthermore, the 2nd call doesn't provide much overhead here IMO since it will see that the access token is not expired and simply return without re-authorizing.

This double call doesn't happen when an OAuth2AuthorizedClient is registered using .attributes(oauth2AuthorizedClient(client)) (which is not an option in my use case, I don't have the client at hand to set it like this).

See the reference documentation on how to obtain the OAuth2AuthorizedClient.