-
Notifications
You must be signed in to change notification settings - Fork 6.2k
Ensure ID Token is updated after refresh token (Reactive) #17246
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
base: main
Are you sure you want to change the base?
Conversation
|
Thanks for your patience @evgeniycheban and apologies that I could not get this into I have scheduled this for |
be68a71 to
ac99b3b
Compare
|
Hi @jgrandja, I have rebased my branch and also updated license headers to meet new pattern, let me know your feedback once you review it, thanks. |
|
@evgeniycheban I took a look at the PR and noticed that the implementation is different compared to the Servlet implementation.
The preference is to keep the Servlet and Reactive implementations as close as possible. Is there a reason you did not make use of Just a heads up that I'm on PTO starting tomorrow and returning Jan 5 so I'll reply when I return and we can work through this. |
|
Hi @jgrandja, thank you for reviewing it. The reason I did not use the |
|
@evgeniycheban Got it. I'll do a detailed review next week and we can work towards merging this. |
jgrandja
left a comment
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.
Thank you for your patience @evgeniycheban.
Overall, this looks good and I believe we are fairly close to merging.
Please see my review comments for further updates.
|
|
||
| private ReactiveOAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = new WebClientReactiveRefreshTokenTokenResponseClient(); | ||
|
|
||
| private ReactiveOAuth2AuthorizationSuccessHandler refreshTokenSuccessHandler = new RefreshTokenReactiveOAuth2AuthorizationSuccessHandler(); |
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.
ReactiveOAuth2AuthorizationSuccessHandler is not designed to be used with a ReactiveOAuth2AuthorizedClientProvider and instead is meant to be used with a ReactiveOAuth2AuthorizedClientManager.
Can you re-work this and set RefreshTokenReactiveOAuth2AuthorizationSuccessHandler in DefaultReactiveOAuth2AuthorizedClientManager ?
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
| * Connect Core 1.0 - Section 12.2 Successful Refresh Response</a> | ||
| * | ||
| * @author Evgeniy Cheban | ||
| * @since 7.0 |
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.
Update to 7.1
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
| @Override | ||
| public Mono<Void> onAuthorizationSuccess(OAuth2AuthorizedClient authorizedClient, Authentication principal, | ||
| Map<String, Object> attributes) { | ||
| if (!(principal instanceof OAuth2AuthenticationToken authenticationToken) |
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.
OidcAuthorizedClientRefreshedEventListener performs a couple of validation checks that are missing here. Specifically, checks OAuth2AccessTokenResponse to ensure openid scope is associated with the access token and an id_token is returned. These pre-checks should be in place as we only want to proceed if this is an OIDC refresh token flow.
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.
| .transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono)); | ||
| // Re-request an Authentication from serverSecurityContextRepository since it | ||
| // might have been changed during provider invocation. | ||
| return effectiveAuthentication(request).flatMap((authentication) -> next.exchange(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.
Instead of adding this logic, can we not update the SecurityContext in the reactor context in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler right after this.serverSecurityContextRepository.save(exchange, securityContext) to avoid this serverSecurityContextRepository.load()?
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.
The tricky part here is that we can't propagate the refreshed SecurityContext to the upstream in a reactor context, it is propagated always one-way from upstream to downstream, so that's why I used saving the refreshed SecurityContext in ServerSecurityContextRepository to be able later to continue the reactive chain with the refreshed SecurityContext and propagate it using contextWrite.
I also considered using a custom CoreSubscriber similar to how it's done in SecurityReactorContextConfiguration, but I'm not sure if it would be the right approach here.
What do you think?
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.
it is propagated always one-way from upstream to downstream
I'm not sure I understand this?
The tricky part here is that we can't propagate the refreshed
SecurityContextto the upstream in a reactor context
Please see the following issue to see if it helps?
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 will have a look at context-propagation library, thank you.
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 get the point you provided here @jgrandja, however, in order to propagate the refreshed SecurityContext to the next ExchangeFilterFunction in ServerOAuth2AuthorizedClientExchangeFilterFunction:
return next.exchange(request)
.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono));updating SecurityContext in the reactor context in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler has no effect, so we need to write the new context to the reactive chain before calling next.exchange(request):
return effectiveAuthentication(request).flatMap((authentication) -> next.exchange(request)
.transform((responseMono) -> this.clientResponseHandler.handleResponse(request, responseMono))
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication)));At this point we load the SecurityContext to get the most recent version to be passed to the downstream.
Another option could be moving the logic from RefreshTokenReactiveOAuth2AuthorizationSuccessHandler to RefreshTokenReactiveOAuth2AuthorizedClientProvider and enriching an OAuth2AuthorizedClient with the new Authentication object when the token is refreshed and then propagate it to the downstream to the next ExchangeFilterFunction in ServerOAuth2AuthorizedClientExchangeFilterFunction.
@jgrandja, I'm also available for a short call if you want to discuss this in more details, just let me know, thanks.
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.
@jgrandja I can as well commit the change that would demonstrate the failing test if we do .contextWrite in RefreshTokenReactiveOAuth2AuthorizationSuccessHandler instead of ServerOAuth2AuthorizedClientExchangeFilterFunction, please let me know.
| * @param serverSecurityContextRepository the {@link ServerSecurityContextRepository} | ||
| * to use | ||
| */ | ||
| public void setServerSecurityContextRepository(ServerSecurityContextRepository serverSecurityContextRepository) { |
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 move the public methods above the private methods.
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.
8874f50 to
1ea9456
Compare
1ea9456 to
4e1f26c
Compare
4e1f26c to
0590138
Compare
Closes spring-projectsgh-17188 Signed-off-by: Evgeniy Cheban <[email protected]>
0590138 to
4356af2
Compare
Closes gh-17188