-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
WebClient support should get new access token when expired and client_credentials #6127
WebClient support should get new access token when expired and client_credentials #6127
Conversation
@@ -245,13 +249,26 @@ public void setAccessTokenExpiresSkew(Duration accessTokenExpiresSkew) { | |||
} | |||
|
|||
private Mono<OAuth2AuthorizedClient> refreshIfNecessary(ClientRequest request, ExchangeFunction next, OAuth2AuthorizedClient authorizedClient) { | |||
if (shouldRefresh(authorizedClient)) { | |||
ClientRegistration clientRegistration = authorizedClient.getClientRegistration(); | |||
if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType()) && hasExpired(authorizedClient)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if a helper method would be better here? Something like
isClientCredentialsGrantType(authorizedClient)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
bb73377
to
756837d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @warrenbailey! This looks good and we should be able to merge after you apply some of the minor updates. See my comments inline.
return hasExpired(authorizedClient); | ||
} | ||
|
||
private boolean hasExpired(OAuth2AuthorizedClient authorizedClient) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename the method to hasTokenExpired
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
this(authorizedClientRepository, new OAuth2AuthorizedClientResolver(clientRegistrationRepository, authorizedClientRepository)); | ||
} | ||
|
||
protected ServerOAuth2AuthorizedClientExchangeFilterFunction(ServerOAuth2AuthorizedClientRepository authorizedClientRepository, OAuth2AuthorizedClientResolver authorizedClientResolver) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove protected
so it's package-private instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType()); | ||
} | ||
|
||
private Mono<OAuth2AuthorizedClient> getNewAuthorizedClient(ClientRegistration clientRegistration, OAuth2AuthorizedClientResolver.Request request) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename the method to authorizeWithClientCredentials
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
return hasExpired(authorizedClient); | ||
} | ||
|
||
private boolean hasExpired(OAuth2AuthorizedClient authorizedClient) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename the method to hasTokenExpired
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -423,6 +424,80 @@ public void filterWhenRefreshRequiredThenRefresh() { | |||
assertThat(getBody(request1)).isEmpty(); | |||
} | |||
|
|||
@Test | |||
public void filterWhenClientCredentialsNotExpiresDoesNotGetNewToken() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename the method to filterWhenClientCredentialsTokenNotExpiredThenUseCurrentToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
|
||
@Test | ||
public void filterWhenClientCredentialsHasNotExpiredDoNotGetNewToken() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename the method to filterWhenClientCredentialsTokenNotExpiredThenUseCurrentToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -144,6 +149,88 @@ public void filterWhenExistingAuthorizationThenSingleAuthorizationHeader() { | |||
assertThat(headers.get(HttpHeaders.AUTHORIZATION)).containsOnly("Bearer " + this.accessToken.getTokenValue()); | |||
} | |||
|
|||
@Test | |||
public void filterWhenClientCredentialsExpiresGetNewToken() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename the method to filterWhenClientCredentialsTokenExpiredThenGetNewToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
this.function = new ServerOAuth2AuthorizedClientExchangeFilterFunction(this.authorizedClientRepository, this.oAuth2AuthorizedClientResolver); | ||
|
||
OAuth2AccessToken newOauth2AccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to newAccessToken
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
when(this.oAuth2AuthorizedClientResolver.clientCredentials(any(), any(), any())).thenReturn(Mono.just(newAuthorizedClient)); | ||
when(this.oAuth2AuthorizedClientResolver.createDefaultedRequest(any(), any(), any())).thenReturn(Mono.just(r)); | ||
|
||
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any())).thenReturn(Mono.empty()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, otherwise I end up with a null pointer in the authorizedWithClientCredentials method line 273
@@ -133,7 +133,7 @@ public void setClientCredentialsTokenResponseClient( | |||
}); | |||
} | |||
|
|||
private Mono<? extends OAuth2AuthorizedClient> clientCredentials( | |||
public Mono<OAuth2AuthorizedClient> clientCredentials( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should expose a public API here. What you can do instead of:
when(this.oAuth2AuthorizedClientResolver.clientCredentials(any(), any(), any())).thenReturn(Mono.just(newAuthorizedClient));
try this
Mock ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest>
and set it via OAuth2AuthorizedClientResolver.setClientCredentialsTokenResponseClient()
and you'll also have to spy(oAuth2AuthorizedClientResolver)
Makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I'll have a think about how it could be reworked.
Though I made it public as I need access in the authorizeWithClientCredentials method in ServerOAuthorizedCientExchangeFilterFunction not just for tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
on second review, instead of what I suggested previously for the test, let's just change the method signature to:
Mono<OAuth2AuthorizedClient> clientCredentials
(with package-private access)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, i'll do that now
Thanks for the feedback @jgrandja I'll make those changes |
Thanks for the updates @warrenbailey! There are a couple of comments I provided. After those are resolved please squash the commits to 1 and format the commit message to include "Fixes gh-5893". |
…ials token. Once client credentials access token has expired retrieve a new token from the OAuth2 authorization server. These tokens can't be refreshed because they do not have a refresh token associated with. This is standard behaviour for Oauth 2 client credentails
27d1a2b
to
321bb75
Compare
Thank you @warrenbailey. This is now in master! I also added a polish commit for this feedback comment. |
Updating the Servlet and Server OAuth2 authorized exchange filter functions so that they get a new access token when the current access token has expired and client_credentials flow is used.
Fixes #5893