Describe the bug Upgrading our codebase to Spring Boot 3.1.1, we got an eror message when calling Salesforce API.

This class supports client_secret_basic, client_secret_post, and none by default. Client [salesforce-client] is using [org.springframework.security.oauth2.core.ClientAuthenticationMethod@3b4abc2b] instead. Please use a supported client authentication method, or use setRequestEntityConverter to supply an instance that supports [org.springframework.security.oauth2.core.ClientAuthenticationMethod@3b4abc2b]

We use private_key_jwt authentication method.

It seems that this is a consequence of #13240

We found a workaround bypassing the decorator:

DefaultJwtBearerTokenResponseClient jwtBearerTokenResponseClient = new DefaultJwtBearerTokenResponseClient();
jwtBearerTokenResponseClient.setRequestEntityConverter(new JwtBearerGrantRequestEntityConverter());
jwtBearerOAuth2AuthorizedClientProvider.setAccessTokenResponseClient(jwtBearerTokenResponseClient);

After bypassing the decorator it works well, as before the modification.

To Reproduce

Here is the configuration of our security:

 @Bean
 public OAuth2AuthorizedClientManager authorizedJwtBearerClientManagerJwt(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientService authorizedClientService) {

        JwtBearerOAuth2AuthorizedClientProvider jwtBearerOAuth2AuthorizedClientProvider =
                new JwtBearerOAuth2AuthorizedClientProvider();
        jwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver(this::resolveJwtAssertion);

        bypassClientAuthenticationMethodValidatingRequestEntityConverter(jwtBearerOAuth2AuthorizedClientProvider);

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .provider(jwtBearerOAuth2AuthorizedClientProvider)
                        .build();

        AuthorizedClientServiceOAuth2AuthorizedClientManager authorizedClientManager =
                new AuthorizedClientServiceOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientService);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

 @Bean
 public WebClient webClient(
                               @Qualifier("authorizedJwtBearerClientManagerJwt")
                               OAuth2AuthorizedClientManager authorizedJwtBearerClientManagerJwt) {

        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedJwtBearerClientManagerJwt);
        oauth2Client.setDefaultClientRegistrationId("salesforce-client");

        return WebClient.builder()
                .baseUrl(sfdcBaseUrl)
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }

Comment From: jzheaux

Thanks, @benfonty. This looks like an oversight. I've prioritized it for the next maintenance release.

Comment From: jzheaux

Actually, I think my earlier analysis is incorrect.

JwtBearerGrantRequestEntityConverter does not by default support private_key_jwt. It requires further configuration, thus the error message.

To configure JwtBearerGrantRequestEntityConverter to honor private_key_jwt, you should use NimbusJwtClientAuthenticationParametersConverter like so:

JwtBearerGrantRequestEntityConverter requestEntityConverter = new JwtBearerGrantRequestEntityConverter();
requestEntityConverter.setParametersConverter(new NimbusJwtClientAuthenticationParametersConverter(this::keyLookup));

Where keyLookup is a strategy that you provide for discovering your Client's private key to use.

Or, if you don't need to use a JWT for client authentication as well, you can choose a method that is supported out of the box.

To summarize, you can address this error in one of two ways:

  1. Use a supported ClientAuthenticationMethod. As the error states, these are none, client_secret_basic, and client_secret_post.
  2. Configure JwtBearerGrantReqeustEntityConverter: For example, using NimbusJwtClientAuthenticationParametersConverter does the work of adding client_assertion in accordance with this JWT Bearer Grant Type spec.

Comment From: benfonty

Thank you for the feedback.

In the code I provided, this part:

JwtBearerOAuth2AuthorizedClientProvider jwtBearerOAuth2AuthorizedClientProvider = 
                   new JwtBearerOAuth2AuthorizedClientProvider();
jwtBearerOAuth2AuthorizedClientProvider.setJwtAssertionResolver(this::resolveJwtAssertion);

We set the method resolveJwtAssertion, whose responsibility is to build the jwt we used for authentication.

I have the feeling that the goal of the class JwtBearerOAuth2AuthorizedClientProvide was to handle private_key_jwt, and it does it pretty well because it is working well calling Salesforce's Jwt Bearer flow (I didn't check whether it adds all the required parameters but I assume so).

That's why I am a little confused about the fact that we have to use NimbusJwtClientAuthenticationParametersConverter whose goal seems to also provide a jwt parameter.

There must be something I didn't understand here, can you provide some clarification?

Comment From: jzheaux

There are two assertions in the JWT Bearer grant type: The bearer assertion and the client assertion. The first is the grant and the second identifies the client.

JwtBearerOAuth2AuthorizedClientProvider#jwtAssertionResolver is for adding the bearer assertion.

NimbusJwtClientAuthenticationParametersConverter is for adding the client assertion.

This can be confusing, though, since the bearer assertion can be used to identify the client, making the client assertion redundant. This nuance is identified in the spec:

Authentication of the client is optional, as described in [Section 3.2.1] ...

So, if you are using jwtAssertionResolver to identify the client, no further client authentication is needed, and ClientAuthenticationMethod.NONE is the most correct.

On the other hand, if your bearer assertion does not identify the client, then you should add a client assertion as well. This is when you would use NimbusJwtClientAuthenticationParametersConverter.

Comment From: benfonty

I just made some tests using ClientAuthenticationMethod.NONE and disabling the workaround we made and it seems to work properly. Thank you for your clarification.

Comment From: jzheaux

Glad you got it sorted out! Happy to have helped.