I have a legacy OAuth2 API I need to use, and it requires the use of a password grant. Support for password grants was removed in AbstractRestClientOAuth2AccessTokenResponseClient so when I tried to migrate my OAuth2 configuration to use the new RestClient based OAuth2AccessTokenResponseClient I could not. At least not for the password grant.

I tried to implement my own, but the constructor of AbstractRestClientOAuth2AccessTokenResponseClient is package private so it cannot be extended. I did not want to copy all the code, as that is a maintainability nightmare.

Consider making AbstractRestClientOAuth2AccessTokenResponseClient extensible for this use case, even though password grants are deprecated there are still many out in the wild.


As a workaround I've created the package org.springframework.security.oauth2.client.endpoint in my own project, which allows me to extend. Obviously that doesn't work with JPMS - and it's also a nasty hack.

Comment From: sjohnr

Thanks for reaching out @bmorris591!

This issue is very similar to gh-14657 so I'm going to close this as a duplicate. Having said that, in Spring Security 6.4 (currently available in 6.4.0-RC1 and 6.4.0-SNAPSHOT), you can directly implement a minimal version of OAuth2AccessTokenResponseClient and re-use DefaultOAuth2TokenRequestHeadersConverter and DefaultOAuth2TokenRequestParametersConverter as in the following example:

public final class RestClientPasswordTokenResponseClient
        implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {

    private final RestClient restClient = RestClient.builder()
        .messageConverters((messageConverters) -> {
            messageConverters.clear();
            messageConverters.add(new FormHttpMessageConverter());
            messageConverters.add(new OAuth2AccessTokenResponseHttpMessageConverter());
        })
        .defaultStatusHandler(new OAuth2ErrorResponseErrorHandler())
        .build();

    private final Converter<OAuth2PasswordGrantRequest, HttpHeaders> headersConverter =
        new DefaultOAuth2TokenRequestHeadersConverter<>();

    private final Converter<OAuth2PasswordGrantRequest, MultiValueMap<String, String>> parametersConverter =
        new DefaultOAuth2TokenRequestParametersConverter<>();

    @Override
    public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest grantRequest) {
        try {
            return this.restClient.post()
                .uri(grantRequest.getClientRegistration().getProviderDetails().getTokenUri())
                .headers((h) -> h.putAll(this.headersConverter.convert(grantRequest)))
                .body(this.parametersConverter.convert(grantRequest))
                .retrieve()
                .body(OAuth2AccessTokenResponse.class);
        }
        catch (RestClientException ex) {
            String errorMessage = "An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: %s"
                .formatted(ex.getMessage());
            OAuth2Error error = new OAuth2Error("invalid_token_response", errorMessage, null);
            throw new OAuth2AuthorizationException(error, ex);
        }
    }

}

If you do, please keep in mind that the OAuth2PasswordGrantRequest class is deprecated and will be removed in Spring Security 7.

Comment From: sjohnr

Also note that DefaultPasswordTokenResponseClient is deprecated but still available until Spring Security 7.

Comment From: bmorris591

Also note that DefaultPasswordTokenResponseClient is deprecated but still available until Spring Security 7.

Yeah - I was trying to migrate everything to RestClient so it was nice and "neat". Thanks for your hint on the simply impl - seems to work in tests. When the time comes for Spring 7 I will need to revisit.