Skip to content

Add support for customizing the Access Token Request #5656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

jgrandja
Copy link
Contributor

This PR resolves #5547 and #5466

The 1st commit pushes most of the code from DefaultClientCredentialsTokenResponseClient to an abstract implementation AbstractOAuth2AccessTokenResponseClient to allow for reuse in the next commit.

The 2nd commit adds a new implementation of OAuth2AccessTokenResponseClient for the authorization_code grant DefaultAuthorizationCodeTokenResponseClient - extending AbstractOAuth2AccessTokenResponseClient.

The 3rd commit makes DefaultAuthorizationCodeTokenResponseClient the default client replacing the previous default NimbusAuthorizationCodeTokenResponseClient.

}

@Override
public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) throws OAuth2AuthenticationException {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename parameter authorizationGrantRequest to accessTokenRequest and mark it final

@rwinch
Copy link
Member

rwinch commented Aug 14, 2018

At a high level, I don't really like the idea of the AbstractOAuth2AccessTokenResponseClient. It seems to be introducing additional APIs that are already available in RestTemplate. For example, there is a buildRequestMethod which is already supported in RestTemplate using a MessageConverter. The conversion of the error response is already possible using a ResponseErrorHandler. The parsing of the response is possible using a MessageConverter as well. The disadvantage is that we now need specific hooks for each request type times each type of customization part of the request that needs customized (i.e. headers, body, etc).

I propose the code be refactored to be exposed using the existing APIs within RestTemplate. The DefaultClientCredentialsTokenResponseClient and DefaultAuthorizationCodeTokenResponseClient can then use a RestTemplate with the appropriate API implementations registered by default. User's would then be required to provide a RestOperations implementation that can convert from a request/response to the OAuth2AccessTokenResponseClient.

Note that proper setup of the RestTempalte is already required since the AbstractOAuth2AccessTokenResponseClient relies on a NoOpResponseErrorHandler being set to handle the errors. As it stands the user cannot inject a custom RestOperations and expect this to work without creating their own NoOpResponseErrorHandler.

Copy link
Member

@rwinch rwinch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see #5656 (comment)

@jgrandja
Copy link
Contributor Author

@rwinch Ok this makes sense as far as leveraging HttpMessageConverter for reading OAuth2AccessTokenResponse on a success response and reading OAuth2Error using ResponseErrorHandler on error response.

Can you clarify:

there is a buildRequestMethod which is already supported in RestTemplate using a MessageConverter.

I can't seem to find buildRequestMethod? Did you mean to leverage RequestCallback?

@rwinch
Copy link
Member

rwinch commented Aug 20, 2018

Sorry that was a typo. I meant to type buildRequest method.

To be clear...I'm not certain which pieces of RestOperations/RestTemplate make the most sense for us to leverage. What seems clear to me is that there are enough APIs for us to leverage within RestTemplate already.

* @param authorizationGrantType the authorization grant type
* @param clientRegistration the client registration
*/
protected AbstractOAuth2AuthorizationGrantRequest(
Copy link
Member

@rwinch rwinch Aug 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this change necessary for this PR? I'm not sure it adds a lot especially with the value being optional vs in subclasses we know it is non-null. At minimum, I'd like to consider this change separately if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed this change can be removed

import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.oauth2.client.endpoint.converter.OAuth2AuthorizationCodeGrantRequestEntityConverter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a package tangle because a parent package is accessing a subpackage of itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm wondering if to fix the tangle if we could move OAuth2AuthorizationCodeGrantRequestEntityConverter and OAuth2ClientCredentialsGrantRequestEntityConverter to org.springframework.security.oauth2.core.http.converter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OAuth2AuthorizationCodeGrantRequest and OAuth2ClientCredentialsGrantRequest reside in the client.endpoint package so we should keep it in the client module.

The 2 options I can think of right now are:
org.springframework.security.oauth2.client.endpoint (keep in same package)
org.springframework.security.oauth2.client.converter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I think given it is associated with RequestEntity, we should consider a variation of what you proposed. What do you think of org.springframework.security.oauth2.client.http.converter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that makes sense....let's go with org.springframework.security.oauth2.client.http.converter

* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request (Authorization Code Grant)</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response (Authorization Code Grant)</a>
*/
public class DefaultAuthorizationCodeTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the methods are already final, let's make the class final

*
* @param restOperations the {@link RestOperations} used when requesting the access token response
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
*/
public final void setRestOperations(RestOperations restOperations) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can someone inject the RestOperations and still get valid error handling? I think we may need to make OAuth2ErrorResponseErrorHandler public.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document what the RestOperations required (i.e. the types of converters it would need, error handling, etc)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of exposing OAuth2ErrorResponseErrorHandler but the implementation was pretty easy so decided to hide it.

Either way, I'm fine with exposing it.

* @param authorizationCodeGrantRequest the authorization code grant request
* @return the {@link HttpHeaders} used for the Access Token Request
*/
protected HttpHeaders buildHeaders(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like this to be private. User's that need to customize the headers can do so by delegation to this class and constructing a modified RequestEntity.

* @param authorizationCodeGrantRequest the authorization code grant request
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
*/
protected MultiValueMap<String, String> buildFormParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like this to be private. User's that need to customize the headers can do so by delegation to this class and constructing a modified RequestEntity.

* @param clientCredentialsGrantRequest the client credentials grant request
* @return the {@link HttpHeaders} used for the Access Token Request
*/
protected HttpHeaders buildHeaders(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like this to be private. User's that need to customize the headers can do so by delegation to this class and constructing a modified RequestEntity.

* @param clientCredentialsGrantRequest the client credentials grant request
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
*/
protected MultiValueMap<String, String> buildFormParameters(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like this to be private. User's that need to customize the headers can do so by delegation to this class and constructing a modified RequestEntity.

*
* @param restOperations the {@link RestOperations} used when requesting the access token response
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
*/
public final void setRestOperations(RestOperations restOperations) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should document what the RestOperations required (i.e. the types of converters it would need, error handling, etc)

*
* @param restOperations the {@link RestOperations} used when requesting the Access Token Response
*/
public final void setRestOperations(RestOperations restOperations) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as the other setRestOperations

import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.security.oauth2.client.endpoint.converter.OAuth2AuthorizationCodeGrantRequestEntityConverter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm wondering if to fix the tangle if we could move OAuth2AuthorizationCodeGrantRequestEntityConverter and OAuth2ClientCredentialsGrantRequestEntityConverter to org.springframework.security.oauth2.core.http.converter

@jgrandja
Copy link
Contributor Author

@rwinch I've applied the updates as discussed. I've also rebased/squashed to 1 commit and excluded the changes to DefaultClientCredentialsTokenResponseClient. After this is merged, I'll resolve #5735

import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.http.converter.OAuth2AuthorizationCodeGrantRequestEntityConverter;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this is another package tangle. The client.endpoint package refers to http.converter above and http.converter refers to client.endpoint

@jgrandja
Copy link
Contributor Author

jgrandja commented Aug 27, 2018

@rwinch DefaultAuthorizationCodeTokenResponseClient and OAuth2AuthorizationCodeGrantRequest reside in the client.endpoint so I moved OAuth2AuthorizationCodeGrantRequestEntityConverter to the same package as this made the most sense rather than it's own http.converter package.

DefaultAuthorizationCodeTokenResponseClient uses OAuth2AuthorizationCodeGrantRequestEntityConverter which uses OAuth2AuthorizationCodeGrantRequest.

@rwinch
Copy link
Member

rwinch commented Aug 27, 2018

Thanks for the change. Overall it looks good. Before we merge:

  • Please squash
  • Please rebase off master and ensure the build passes as there have been some recent checkstyle changes

Once that is done and the build passes, please feel free to merge.

@jgrandja
Copy link
Contributor Author

Thanks @rwinch. This is now in master.

@jgrandja jgrandja closed this Aug 27, 2018
@jgrandja jgrandja deleted the gh-5547-restops-token-client branch August 27, 2018 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add RestOperations implementation of OAuth2AccessTokenResponseClient
3 participants