diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java index 8ec6fdb17e4..3d295ea3e45 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/InMemoryOAuth2AuthorizedClientService.java @@ -86,7 +86,8 @@ public T loadAuthorizedClient(String clientRe return null; } return (T) new OAuth2AuthorizedClient(registration, cachedAuthorizedClient.getPrincipalName(), - cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken()); + cachedAuthorizedClient.getAccessToken(), cachedAuthorizedClient.getRefreshToken(), + cachedAuthorizedClient.getAttributes()); } @Override diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java index 35a670f995a..7c9e6cc3de4 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizationContext.java @@ -60,6 +60,8 @@ public final class OAuth2AuthorizationContext { */ public static final String PASSWORD_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".PASSWORD"); + public static final String ADDITIONAL_GRANT_REQUEST_PARAMETERS_ATTRIBUTE_NAME = OAuth2AuthorizationContext.class.getName().concat(".ADDITIONAL_GRANT_REQUEST_PARAMETERS"); + private ClientRegistration clientRegistration; private OAuth2AuthorizedClient authorizedClient; diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java index 9d9efe1343c..07a501e034a 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/OAuth2AuthorizedClient.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.client; import java.io.Serializable; +import java.util.Map; import org.springframework.lang.Nullable; import org.springframework.security.core.SpringSecurityCoreVersion; @@ -53,6 +54,8 @@ public class OAuth2AuthorizedClient implements Serializable { private final OAuth2RefreshToken refreshToken; + private final Map attributes; + /** * Constructs an {@code OAuth2AuthorizedClient} using the provided parameters. * @param clientRegistration the authorized client's registration @@ -73,6 +76,20 @@ public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String prin */ public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName, OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) { + this(clientRegistration, principalName, accessToken, refreshToken, null); + } + + /** + * Constructs an {@code OAuth2AuthorizedClient} using the provided parameters. + * @param clientRegistration the authorized client's registration + * @param principalName the name of the End-User {@code Principal} (Resource Owner) + * @param accessToken the access token credential granted + * @param refreshToken the refresh token credential granted + * @param attributes associated with the client + */ + public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String principalName, + OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken, + @Nullable Map attributes) { Assert.notNull(clientRegistration, "clientRegistration cannot be null"); Assert.hasText(principalName, "principalName cannot be empty"); Assert.notNull(accessToken, "accessToken cannot be null"); @@ -80,6 +97,7 @@ public OAuth2AuthorizedClient(ClientRegistration clientRegistration, String prin this.principalName = principalName; this.accessToken = accessToken; this.refreshToken = refreshToken; + this.attributes = attributes; } /** @@ -115,4 +133,21 @@ public OAuth2AccessToken getAccessToken() { return this.refreshToken; } + /** + * Returns the {@link Map} attributes. + * @return the {@link Map} + * @since 6.5 + */ + public @Nullable Map getAttributes() { + return this.attributes; + } + + @Nullable + @SuppressWarnings("unchecked") + public T getAttribute(String name) { + if (this.getAttributes() == null) { + return null; + } + return (T) this.getAttributes().get(name); + } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java index ca22416af95..4f695e1bb7e 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/TokenExchangeOAuth2AuthorizedClientProvider.java @@ -19,6 +19,7 @@ import java.time.Clock; import java.time.Duration; import java.time.Instant; +import java.util.*; import java.util.function.Function; import org.springframework.lang.Nullable; @@ -73,9 +74,19 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) { if (!AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType())) { return null; } + + Map contextAdditionalGrantRequestParameters = + context.getAttribute(OAuth2AuthorizationContext.ADDITIONAL_GRANT_REQUEST_PARAMETERS_ATTRIBUTE_NAME); + OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient(); - if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) { - // If client is already authorized but access token is NOT expired than no + Map authorizedClientAdditionalGrantRequestParameters = + authorizedClient != null + ? authorizedClient.getAttribute(OAuth2AuthorizationContext.ADDITIONAL_GRANT_REQUEST_PARAMETERS_ATTRIBUTE_NAME) + : null; + if (authorizedClient != null + && !hasTokenExpired(authorizedClient.getAccessToken()) + && Objects.equals(authorizedClientAdditionalGrantRequestParameters, contextAdditionalGrantRequestParameters)) { + // If client is already authorized but access token is NOT expired and the attributes are equal, then no // need for re-authorization return null; } @@ -86,11 +97,14 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) { OAuth2Token actorToken = this.actorTokenResolver.apply(context); TokenExchangeGrantRequest grantRequest = new TokenExchangeGrantRequest(clientRegistration, subjectToken, - actorToken); + actorToken, contextAdditionalGrantRequestParameters); OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, grantRequest); + Map authorizedClientAttributes = new HashMap<>(); + authorizedClientAttributes.put(OAuth2AuthorizationContext.ADDITIONAL_GRANT_REQUEST_PARAMETERS_ATTRIBUTE_NAME, contextAdditionalGrantRequestParameters); + return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(), - tokenResponse.getAccessToken(), tokenResponse.getRefreshToken()); + tokenResponse.getAccessToken(), tokenResponse.getRefreshToken(), authorizedClientAttributes); } private OAuth2Token resolveSubjectToken(OAuth2AuthorizationContext context) { diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java index d225a5c91bc..7f39e0589c3 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/endpoint/TokenExchangeGrantRequest.java @@ -27,6 +27,8 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; +import java.util.Map; + /** * A Token Exchange Grant request that holds the {@link OAuth2Token subject token} and * optional {@link OAuth2Token actor token}. @@ -53,6 +55,8 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR private final OAuth2Token actorToken; + private final Map additionalParameters; + /** * Constructs a {@code TokenExchangeGrantRequest} using the provided parameters. * @param clientRegistration the client registration @@ -61,12 +65,24 @@ public class TokenExchangeGrantRequest extends AbstractOAuth2AuthorizationGrantR */ public TokenExchangeGrantRequest(ClientRegistration clientRegistration, OAuth2Token subjectToken, OAuth2Token actorToken) { + this(clientRegistration,subjectToken,actorToken,null); + } + + /** + * Constructs a {@code TokenExchangeGrantRequest} using the provided parameters. + * @param clientRegistration the client registration + * @param subjectToken the subject token + * @param actorToken the actor token + */ + public TokenExchangeGrantRequest(ClientRegistration clientRegistration, OAuth2Token subjectToken, + OAuth2Token actorToken, Map additionalParameters) { super(AuthorizationGrantType.TOKEN_EXCHANGE, clientRegistration); Assert.isTrue(AuthorizationGrantType.TOKEN_EXCHANGE.equals(clientRegistration.getAuthorizationGrantType()), "clientRegistration.authorizationGrantType must be AuthorizationGrantType.TOKEN_EXCHANGE"); Assert.notNull(subjectToken, "subjectToken cannot be null"); this.subjectToken = subjectToken; this.actorToken = actorToken; + this.additionalParameters = additionalParameters; } /** @@ -85,6 +101,14 @@ public OAuth2Token getActorToken() { return this.actorToken; } + /** + * Returns the {@link Map additional parameters}. + * @return the {@link Map additional parameters} + */ + public Map getAdditionalParameters() { + return this.additionalParameters; + } + /** * Populate default parameters for the Token Exchange Grant. * @param grantRequest the authorization grant request