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
DefaultPasswordTokenResponseClientis 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.