Browse Source

Token Exchange review updates

Issue gh-60
Steve Riesenberg 1 năm trước cách đây
mục cha
commit
2f1f45bc01
21 tập tin đã thay đổi với 290 bổ sung379 xóa
  1. 4 4
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java
  2. 0 53
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ActorAuthenticationToken.java
  3. 24 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java
  4. 2 10
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  5. 3 11
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java
  6. 2 10
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java
  7. 2 12
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java
  8. 70 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java
  9. 49 44
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java
  10. 45 44
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java
  11. 20 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java
  12. 0 46
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DelegatingOAuth2TokenCustomizer.java
  13. 2 30
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java
  14. 0 82
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeTokenCustomizers.java
  15. 6 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java
  16. 7 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java
  17. 5 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java
  18. 24 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java
  19. 2 11
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  20. 18 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java
  21. 5 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java

+ 4 - 4
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java

@@ -43,8 +43,8 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
 import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
 import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
 import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
 import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
 import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
 import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ActorAuthenticationToken;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CompositeAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
 import org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationServerJackson2Module;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 import org.springframework.security.web.authentication.WebAuthenticationDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetails;
@@ -111,9 +111,9 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
 							TypeReference.of(OidcIdToken.class),
 							TypeReference.of(OidcIdToken.class),
 							TypeReference.of(AbstractOAuth2Token.class),
 							TypeReference.of(AbstractOAuth2Token.class),
 							TypeReference.of(OidcUserInfo.class),
 							TypeReference.of(OidcUserInfo.class),
-							TypeReference.of(OAuth2ActorAuthenticationToken.class),
+							TypeReference.of(OAuth2TokenExchangeActor.class),
 							TypeReference.of(OAuth2AuthorizationRequest.class),
 							TypeReference.of(OAuth2AuthorizationRequest.class),
-							TypeReference.of(OAuth2CompositeAuthenticationToken.class),
+							TypeReference.of(OAuth2TokenExchangeCompositeAuthenticationToken.class),
 							TypeReference.of(AuthorizationGrantType.class),
 							TypeReference.of(AuthorizationGrantType.class),
 							TypeReference.of(OAuth2AuthorizationResponseType.class),
 							TypeReference.of(OAuth2AuthorizationResponseType.class),
 							TypeReference.of(OAuth2TokenFormat.class)
 							TypeReference.of(OAuth2TokenFormat.class)

+ 0 - 53
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ActorAuthenticationToken.java

@@ -1,53 +0,0 @@
-/*
- * Copyright 2020-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.oauth2.server.authorization.authentication;
-
-import java.io.Serializable;
-import java.util.Collections;
-
-import org.springframework.security.authentication.AbstractAuthenticationToken;
-import org.springframework.security.core.Authentication;
-import org.springframework.util.Assert;
-
-/**
- * An {@link Authentication} implementation used for the OAuth 2.0 Token Exchange Grant
- * to represent an actor in a composite token (e.g. the "delegation" use case).
- *
- * @author Steve Riesenberg
- * @since 1.3
- * @see OAuth2CompositeAuthenticationToken
- */
-public class OAuth2ActorAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
-
-	private final String name;
-
-	public OAuth2ActorAuthenticationToken(String name) {
-		super(Collections.emptyList());
-		Assert.hasText(name, "name cannot be empty");
-		this.name = name;
-	}
-
-	@Override
-	public Object getPrincipal() {
-		return this.name;
-	}
-
-	@Override
-	public Object getCredentials() {
-		return null;
-	}
-}

+ 24 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java

@@ -17,12 +17,16 @@ package org.springframework.security.oauth2.server.authorization.authentication;
 
 
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.ClaimAccessor;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 
 
 /**
 /**
  * Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s.
  * Utility methods for the OAuth 2.0 {@link AuthenticationProvider}'s.
@@ -74,4 +78,24 @@ final class OAuth2AuthenticationProviderUtils {
 
 
 		return authorizationBuilder.build();
 		return authorizationBuilder.build();
 	}
 	}
+
+	static <T extends OAuth2Token> OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token,
+			OAuth2TokenContext accessTokenContext) {
+
+		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				token.getTokenValue(), token.getIssuedAt(), token.getExpiresAt(),
+				accessTokenContext.getAuthorizedScopes());
+		OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient().getTokenSettings()
+				.getAccessTokenFormat();
+		builder.token(accessToken, (metadata) -> {
+			if (token instanceof ClaimAccessor claimAccessor) {
+				metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims());
+			}
+			metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
+			metadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue());
+		});
+
+		return accessToken;
+	}
+
 }
 }

+ 2 - 10
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@@ -37,7 +37,6 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.session.SessionInformation;
 import org.springframework.security.core.session.SessionInformation;
 import org.springframework.security.core.session.SessionRegistry;
 import org.springframework.security.core.session.SessionRegistry;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -204,15 +203,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
 			this.logger.trace("Generated access token");
 			this.logger.trace("Generated access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
-				generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-		if (generatedAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) ->
-					metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+		OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder,
+				generatedAccessToken, tokenContext);
 
 
 		// ----- Refresh token -----
 		// ----- Refresh token -----
 		OAuth2RefreshToken refreshToken = null;
 		OAuth2RefreshToken refreshToken = null;

+ 3 - 11
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java

@@ -27,7 +27,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -137,22 +136,15 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
 			this.logger.trace("Generated access token");
 			this.logger.trace("Generated access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
-				generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-
 		// @formatter:off
 		// @formatter:off
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 				.principalName(clientPrincipal.getName())
 				.principalName(clientPrincipal.getName())
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizedScopes(authorizedScopes);
 				.authorizedScopes(authorizedScopes);
 		// @formatter:on
 		// @formatter:on
-		if (generatedAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) ->
-					metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+
+		OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder,
+				generatedAccessToken, tokenContext);
 
 
 		OAuth2Authorization authorization = authorizationBuilder.build();
 		OAuth2Authorization authorization = authorizationBuilder.build();
 
 

+ 2 - 10
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java

@@ -25,7 +25,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2DeviceCode;
 import org.springframework.security.oauth2.core.OAuth2DeviceCode;
@@ -213,15 +212,8 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
 			this.logger.trace("Generated access token");
 			this.logger.trace("Generated access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
-				generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-		if (generatedAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) ->
-					metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims()));
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+		OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder,
+				generatedAccessToken, tokenContext);
 
 
 		// ----- Refresh token -----
 		// ----- Refresh token -----
 		OAuth2RefreshToken refreshToken = null;
 		OAuth2RefreshToken refreshToken = null;

+ 2 - 12
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java

@@ -29,7 +29,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
@@ -181,17 +180,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
 			this.logger.trace("Generated access token");
 			this.logger.trace("Generated access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
-				generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-		if (generatedAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) -> {
-				metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
-				metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
-			});
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+		OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder,
+				generatedAccessToken, tokenContext);
 
 
 		// ----- Refresh token -----
 		// ----- Refresh token -----
 		OAuth2RefreshToken currentRefreshToken = refreshToken.getToken();
 		OAuth2RefreshToken currentRefreshToken = refreshToken.getToken();

+ 70 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.oauth2.server.authorization.authentication;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+import org.springframework.security.oauth2.core.ClaimAccessor;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
+import org.springframework.util.Assert;
+
+/**
+ * A {@link ClaimAccessor} used for the OAuth 2.0 Token Exchange Grant to represent an actor in a
+ * {@link OAuth2TokenExchangeCompositeAuthenticationToken} (e.g. the "delegation" use case).
+ *
+ * @author Steve Riesenberg
+ * @since 1.3
+ * @see OAuth2TokenExchangeCompositeAuthenticationToken
+ */
+public final class OAuth2TokenExchangeActor implements ClaimAccessor {
+
+	private final Map<String, Object> claims;
+
+	public OAuth2TokenExchangeActor(Map<String, Object> claims) {
+		Assert.notNull(claims, "claims cannot be null");
+		this.claims = Collections.unmodifiableMap(claims);
+	}
+
+	@Override
+	public Map<String, Object> getClaims() {
+		return this.claims;
+	}
+
+	public String getIssuer() {
+		return getClaimAsString(OAuth2TokenClaimNames.ISS);
+	}
+
+	public String getSubject() {
+		return getClaimAsString(OAuth2TokenClaimNames.SUB);
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof OAuth2TokenExchangeActor other)) {
+			return false;
+		}
+		return Objects.equals(this.claims, other.claims);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(this.claims);
+	}
+
+}

+ 49 - 44
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java

@@ -22,6 +22,7 @@ import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.Set;
 
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.Log;
@@ -31,15 +32,12 @@ import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
-import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
-import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
@@ -47,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
 import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
 import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
@@ -73,6 +72,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 
 
 	private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
 	private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
 
 
+	private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
+
 	private static final String MAY_ACT = "may_act";
 	private static final String MAY_ACT = "may_act";
 
 
 	private final Log logger = LogFactory.getLog(getClass());
 	private final Log logger = LogFactory.getLog(getClass());
@@ -136,11 +137,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 		}
 		}
 
 
-		if (JWT_TOKEN_TYPE_VALUE.equals(tokenExchangeAuthentication.getSubjectTokenType()) &&
-				!Jwt.class.isAssignableFrom(subjectToken.getToken().getClass())) {
-			// TODO: Need a way to validate subject_token_type, since access tokens
-			//  are always stored as OAuth2AccessToken instead of Jwt.
-			//throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
+		if (!isValidTokenType(tokenExchangeAuthentication.getSubjectTokenType(), subjectToken)) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
 		}
 		}
 
 
 		if (subjectAuthorization.getAttribute(Principal.class.getName()) == null) {
 		if (subjectAuthorization.getAttribute(Principal.class.getName()) == null) {
@@ -153,11 +151,11 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 		// As per https://datatracker.ietf.org/doc/html/rfc8693#section-4.4,
 		// As per https://datatracker.ietf.org/doc/html/rfc8693#section-4.4,
 		// The may_act claim makes a statement that one party is authorized to
 		// The may_act claim makes a statement that one party is authorized to
 		// become the actor and act on behalf of another party.
 		// become the actor and act on behalf of another party.
-		String authorizedActorSubject = null;
+		Map<String, Object> authorizedActorClaims = null;
 		if (subjectToken.getClaims() != null &&
 		if (subjectToken.getClaims() != null &&
 				subjectToken.getClaims().containsKey(MAY_ACT) &&
 				subjectToken.getClaims().containsKey(MAY_ACT) &&
 				subjectToken.getClaims().get(MAY_ACT) instanceof Map<?, ?> mayAct) {
 				subjectToken.getClaims().get(MAY_ACT) instanceof Map<?, ?> mayAct) {
-			authorizedActorSubject = (String) mayAct.get(StandardClaimNames.SUB);
+			authorizedActorClaims = (Map<String, Object>) mayAct;
 		}
 		}
 
 
 		OAuth2Authorization actorAuthorization = null;
 		OAuth2Authorization actorAuthorization = null;
@@ -181,19 +179,15 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 				throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 				throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 			}
 			}
 
 
-			if (JWT_TOKEN_TYPE_VALUE.equals(tokenExchangeAuthentication.getActorTokenType()) &&
-					!Jwt.class.isAssignableFrom(actorToken.getToken().getClass())) {
-				// TODO: Need a way to validate actor_token_type, since access tokens
-				//  are always stored as OAuth2AccessToken instead of Jwt.
-				//throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
+			if (!isValidTokenType(tokenExchangeAuthentication.getActorTokenType(), actorToken)) {
+				throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
 			}
 			}
 
 
-			if (StringUtils.hasText(authorizedActorSubject) &&
-					!authorizedActorSubject.equals(actorAuthorization.getPrincipalName())) {
-				throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
+			if (authorizedActorClaims != null) {
+				validateClaims(authorizedActorClaims, actorToken.getClaims(), OAuth2TokenClaimNames.ISS,
+						OAuth2TokenClaimNames.SUB);
 			}
 			}
-		} else if (StringUtils.hasText(authorizedActorSubject) &&
-				!authorizedActorSubject.equals(clientPrincipal.getName())) {
+		} else if (authorizedActorClaims != null) {
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 		}
 		}
 
 
@@ -235,10 +229,6 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 			this.logger.trace("Generated access token");
 			this.logger.trace("Generated access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
-				generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-
 		// @formatter:off
 		// @formatter:off
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 				.principalName(subjectAuthorization.getPrincipalName())
 				.principalName(subjectAuthorization.getPrincipalName())
@@ -247,14 +237,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 				.attribute(Principal.class.getName(), principal);
 				.attribute(Principal.class.getName(), principal);
 		// @formatter:on
 		// @formatter:on
 
 
-		if (generatedAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) -> {
-				metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
-				metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
-			});
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+		OAuth2AccessToken accessToken = OAuth2AuthenticationProviderUtils.accessToken(authorizationBuilder,
+				generatedAccessToken, tokenContext);
 
 
 		OAuth2Authorization authorization = authorizationBuilder.build();
 		OAuth2Authorization authorization = authorizationBuilder.build();
 		this.authorizationService.save(authorization);
 		this.authorizationService.save(authorization);
@@ -274,6 +258,13 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 				registeredClient, clientPrincipal, accessToken, null, additionalParameters);
 				registeredClient, clientPrincipal, accessToken, null, additionalParameters);
 	}
 	}
 
 
+	private static boolean isValidTokenType(String tokenType, OAuth2Authorization.Token<OAuth2Token> token) {
+		String tokenFormat = token.getMetadata(OAuth2TokenFormat.class.getName());
+		return ACCESS_TOKEN_TYPE_VALUE.equals(tokenType) ||
+				JWT_TOKEN_TYPE_VALUE.equals(tokenType) &&
+				OAuth2TokenFormat.SELF_CONTAINED.getValue().equals(tokenFormat);
+	}
+
 	private static Set<String> validateRequestedScopes(RegisteredClient registeredClient, Set<String> requestedScopes) {
 	private static Set<String> validateRequestedScopes(RegisteredClient registeredClient, Set<String> requestedScopes) {
 		for (String requestedScope : requestedScopes) {
 		for (String requestedScope : requestedScopes) {
 			if (!registeredClient.getScopes().contains(requestedScope)) {
 			if (!registeredClient.getScopes().contains(requestedScope)) {
@@ -284,26 +275,40 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
 		return new LinkedHashSet<>(requestedScopes);
 		return new LinkedHashSet<>(requestedScopes);
 	}
 	}
 
 
+	private static void validateClaims(Map<String, Object> expectedClaims, Map<String, Object> actualClaims, String... claimNames) {
+		if (actualClaims == null) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
+		}
+
+		for (String claimName : claimNames) {
+			if (!Objects.equals(expectedClaims.get(claimName), actualClaims.get(claimName))) {
+				throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
+			}
+		}
+	}
+
 	private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization, OAuth2Authorization actorAuthorization) {
 	private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization, OAuth2Authorization actorAuthorization) {
 		Authentication subjectPrincipal = subjectAuthorization.getAttribute(Principal.class.getName());
 		Authentication subjectPrincipal = subjectAuthorization.getAttribute(Principal.class.getName());
-
-		List<Authentication> actorPrincipals = new LinkedList<>();
-		if (actorAuthorization != null) {
-			actorPrincipals.add(new OAuth2ActorAuthenticationToken(actorAuthorization.getPrincipalName()));
+		if (actorAuthorization == null) {
+			if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
+				return compositeAuthenticationToken.getSubject();
+			}
+			return subjectPrincipal;
 		}
 		}
 
 
-		if (subjectPrincipal instanceof OAuth2CompositeAuthenticationToken compositeAuthenticationToken) {
-			// As per https://datatracker.ietf.org/doc/html/rfc8693#section-4.1,
-			// the act claim can be used to represent a chain of delegation,
-			// so we unwrap the original subject and any previous actor(s).
+		// Capture claims for current actor's access token
+		OAuth2TokenExchangeActor currentActor = new OAuth2TokenExchangeActor(
+				actorAuthorization.getAccessToken().getClaims());
+		List<OAuth2TokenExchangeActor> actorPrincipals = new LinkedList<>();
+		actorPrincipals.add(currentActor);
+
+		// Add chain of delegation for previous actor(s) if any
+		if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
 			subjectPrincipal = compositeAuthenticationToken.getSubject();
 			subjectPrincipal = compositeAuthenticationToken.getSubject();
 			actorPrincipals.addAll(compositeAuthenticationToken.getActors());
 			actorPrincipals.addAll(compositeAuthenticationToken.getActors());
-			// TODO: Should we allow delegation-to-impersonation where previous
-			//  actors exist but no actor_token exists on this request?
 		}
 		}
 
 
-		return CollectionUtils.isEmpty(actorPrincipals) ? subjectPrincipal :
-				new OAuth2CompositeAuthenticationToken(subjectPrincipal, actorPrincipals);
+		return new OAuth2TokenExchangeCompositeAuthenticationToken(subjectPrincipal, actorPrincipals);
 	}
 	}
 
 
 	@Override
 	@Override

+ 45 - 44
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java

@@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
 
 
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.HashSet;
-import java.util.List;
+import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
@@ -36,10 +36,6 @@ import org.springframework.util.Assert;
  */
  */
 public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
 public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
 
 
-	private final List<String> resources;
-
-	private final List<String> audiences;
-
 	private final String requestedTokenType;
 	private final String requestedTokenType;
 
 
 	private final String subjectToken;
 	private final String subjectToken;
@@ -50,70 +46,47 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
 
 
 	private final String actorTokenType;
 	private final String actorTokenType;
 
 
+	private final Set<String> resources;
+
+	private final Set<String> audiences;
+
 	private final Set<String> scopes;
 	private final Set<String> scopes;
 
 
 	/**
 	/**
 	 * Constructs an {@code OAuth2TokenExchangeAuthenticationToken} using the provided parameters.
 	 * Constructs an {@code OAuth2TokenExchangeAuthenticationToken} using the provided parameters.
 	 *
 	 *
-	 * @param resources a list of resource URIs
-	 * @param audiences a list audience values
-	 * @param scopes the requested scope(s)
 	 * @param requestedTokenType the requested token type
 	 * @param requestedTokenType the requested token type
 	 * @param subjectToken the subject token
 	 * @param subjectToken the subject token
 	 * @param subjectTokenType the subject token type
 	 * @param subjectTokenType the subject token type
+	 * @param clientPrincipal the authenticated client principal
 	 * @param actorToken the actor token
 	 * @param actorToken the actor token
 	 * @param actorTokenType the actor token type
 	 * @param actorTokenType the actor token type
-	 * @param clientPrincipal the authenticated client principal
+	 * @param resources the requested resource URI(s)
+	 * @param audiences the requested audience value(s)
+	 * @param scopes the requested scope(s)
 	 * @param additionalParameters the additional parameters
 	 * @param additionalParameters the additional parameters
 	 */
 	 */
-	public OAuth2TokenExchangeAuthenticationToken(List<String> resources, List<String> audiences,
-			@Nullable Set<String> scopes, @Nullable String requestedTokenType, String subjectToken,
-			String subjectTokenType, @Nullable String actorToken, @Nullable String actorTokenType,
-			Authentication clientPrincipal, @Nullable Map<String, Object> additionalParameters) {
+	public OAuth2TokenExchangeAuthenticationToken(String requestedTokenType, String subjectToken,
+			String subjectTokenType, Authentication clientPrincipal, @Nullable String actorToken,
+			@Nullable String actorTokenType, @Nullable Set<String> resources, @Nullable Set<String> audiences,
+			@Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
 		super(AuthorizationGrantType.TOKEN_EXCHANGE, clientPrincipal, additionalParameters);
 		super(AuthorizationGrantType.TOKEN_EXCHANGE, clientPrincipal, additionalParameters);
-		Assert.notNull(resources, "resources cannot be null");
-		Assert.notNull(audiences, "audiences cannot be null");
 		Assert.hasText(requestedTokenType, "requestedTokenType cannot be empty");
 		Assert.hasText(requestedTokenType, "requestedTokenType cannot be empty");
 		Assert.hasText(subjectToken, "subjectToken cannot be empty");
 		Assert.hasText(subjectToken, "subjectToken cannot be empty");
 		Assert.hasText(subjectTokenType, "subjectTokenType cannot be empty");
 		Assert.hasText(subjectTokenType, "subjectTokenType cannot be empty");
-		this.resources = resources;
-		this.audiences = audiences;
 		this.requestedTokenType = requestedTokenType;
 		this.requestedTokenType = requestedTokenType;
 		this.subjectToken = subjectToken;
 		this.subjectToken = subjectToken;
 		this.subjectTokenType = subjectTokenType;
 		this.subjectTokenType = subjectTokenType;
 		this.actorToken = actorToken;
 		this.actorToken = actorToken;
 		this.actorTokenType = actorTokenType;
 		this.actorTokenType = actorTokenType;
+		this.resources = Collections.unmodifiableSet(
+				resources != null ? new LinkedHashSet<>(resources) : Collections.emptySet());
+		this.audiences = Collections.unmodifiableSet(
+				audiences != null ? new LinkedHashSet<>(audiences) : Collections.emptySet());
 		this.scopes = Collections.unmodifiableSet(
 		this.scopes = Collections.unmodifiableSet(
 				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
 				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
 	}
 	}
 
 
-	/**
-	 * Returns the list of resource URIs.
-	 *
-	 * @return the list of resource URIs
-	 */
-	public List<String> getResources() {
-		return this.resources;
-	}
-
-	/**
-	 * Returns the list of audience values.
-	 *
-	 * @return the list of audience values
-	 */
-	public List<String> getAudiences() {
-		return this.audiences;
-	}
-
-	/**
-	 * Returns the requested scope(s).
-	 *
-	 * @return the requested scope(s), or an empty {@code Set} if not available
-	 */
-	public Set<String> getScopes() {
-		return this.scopes;
-	}
-
 	/**
 	/**
 	 * Returns the requested token type.
 	 * Returns the requested token type.
 	 *
 	 *
@@ -158,4 +131,32 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
 	public String getActorTokenType() {
 	public String getActorTokenType() {
 		return this.actorTokenType;
 		return this.actorTokenType;
 	}
 	}
+
+	/**
+	 * Returns the requested resource URI(s).
+	 *
+	 * @return the requested resource URI(s), or an empty {@code Set} if not available
+	 */
+	public Set<String> getResources() {
+		return this.resources;
+	}
+
+	/**
+	 * Returns the requested audience value(s).
+	 *
+	 * @return the requested audience value(s), or an empty {@code Set} if not available
+	 */
+	public Set<String> getAudiences() {
+		return this.audiences;
+	}
+
+	/**
+	 * Returns the requested scope(s).
+	 *
+	 * @return the requested scope(s), or an empty {@code Set} if not available
+	 */
+	public Set<String> getScopes() {
+		return this.scopes;
+	}
+
 }
 }

+ 20 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2CompositeAuthenticationToken.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java

@@ -16,10 +16,10 @@
 
 
 package org.springframework.security.oauth2.server.authorization.authentication;
 package org.springframework.security.oauth2.server.authorization.authentication;
 
 
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.List;
 import java.util.List;
+import java.util.Objects;
 
 
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
@@ -33,13 +33,13 @@ import org.springframework.util.Assert;
  * @since 1.3
  * @since 1.3
  * @see OAuth2TokenExchangeAuthenticationToken
  * @see OAuth2TokenExchangeAuthenticationToken
  */
  */
-public class OAuth2CompositeAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
+public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAuthenticationToken {
 
 
 	private final Authentication subject;
 	private final Authentication subject;
 
 
-	private final List<Authentication> actors;
+	private final List<OAuth2TokenExchangeActor> actors;
 
 
-	public OAuth2CompositeAuthenticationToken(Authentication subject, List<Authentication> actors) {
+	public OAuth2TokenExchangeCompositeAuthenticationToken(Authentication subject, List<OAuth2TokenExchangeActor> actors) {
 		super(subject != null ? subject.getAuthorities() : null);
 		super(subject != null ? subject.getAuthorities() : null);
 		Assert.notNull(subject, "subject cannot be null");
 		Assert.notNull(subject, "subject cannot be null");
 		Assert.notNull(actors, "actors cannot be null");
 		Assert.notNull(actors, "actors cannot be null");
@@ -63,7 +63,22 @@ public class OAuth2CompositeAuthenticationToken extends AbstractAuthenticationTo
 		return this.subject;
 		return this.subject;
 	}
 	}
 
 
-	public List<Authentication> getActors() {
+	public List<OAuth2TokenExchangeActor> getActors() {
 		return this.actors;
 		return this.actors;
 	}
 	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (!(obj instanceof OAuth2TokenExchangeCompositeAuthenticationToken other)) {
+			return false;
+		}
+		return super.equals(obj) && Objects.equals(this.subject, other.subject) &&
+				Objects.equals(this.actors, other.actors);
+	}
+
+	@Override
+	public int hashCode() {
+		return Objects.hash(super.hashCode(), this.subject, this.actors);
+	}
+
 }
 }

+ 0 - 46
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DelegatingOAuth2TokenCustomizer.java

@@ -1,46 +0,0 @@
-/*
- * Copyright 2020-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
-
-import java.util.Collections;
-import java.util.List;
-
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
-import org.springframework.util.Assert;
-
-/**
- * @author Steve Riesenberg
- * @since 1.3
- */
-final class DelegatingOAuth2TokenCustomizer<T extends OAuth2TokenContext> implements OAuth2TokenCustomizer<T> {
-
-	private final List<OAuth2TokenCustomizer<T>> tokenCustomizers;
-
-	DelegatingOAuth2TokenCustomizer(List<OAuth2TokenCustomizer<T>> tokenCustomizers) {
-		Assert.notEmpty(tokenCustomizers, "tokenCustomizers cannot be empty");
-		this.tokenCustomizers = Collections.unmodifiableList(tokenCustomizers);
-	}
-
-	@Override
-	public void customize(T context) {
-		for (OAuth2TokenCustomizer<T> tokenCustomizer : this.tokenCustomizers) {
-			tokenCustomizer.customize(context);
-		}
-	}
-
-}

+ 2 - 30
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java

@@ -15,8 +15,6 @@
  */
  */
 package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
 package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
 
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 import com.nimbusds.jose.jwk.source.JWKSource;
 import com.nimbusds.jose.jwk.source.JWKSource;
@@ -172,39 +170,13 @@ final class OAuth2ConfigurerUtils {
 	}
 	}
 
 
 	private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
 	private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
-		OAuth2TokenCustomizer<JwtEncodingContext> defaultTokenCustomizer = OAuth2TokenExchangeTokenCustomizers.jwt();
 		ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
 		ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
-		OAuth2TokenCustomizer<JwtEncodingContext> userTokenCustomizer = getOptionalBean(httpSecurity, type);
-
-		OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer;
-		if (userTokenCustomizer != null) {
-			List<OAuth2TokenCustomizer<JwtEncodingContext>> tokenCustomizers = new ArrayList<>();
-			tokenCustomizers.add(defaultTokenCustomizer);
-			tokenCustomizers.add(userTokenCustomizer);
-			tokenCustomizer = new DelegatingOAuth2TokenCustomizer<>(tokenCustomizers);
-		} else {
-			tokenCustomizer = defaultTokenCustomizer;
-		}
-
-		return tokenCustomizer;
+		return getOptionalBean(httpSecurity, type);
 	}
 	}
 
 
 	private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {
 	private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {
-		OAuth2TokenCustomizer<OAuth2TokenClaimsContext> defaultTokenCustomizer = OAuth2TokenExchangeTokenCustomizers.accessToken();
 		ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
 		ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
-		OAuth2TokenCustomizer<OAuth2TokenClaimsContext> userTokenCustomizer = getOptionalBean(httpSecurity, type);
-
-		OAuth2TokenCustomizer<OAuth2TokenClaimsContext> tokenCustomizer;
-		if (userTokenCustomizer != null) {
-			List<OAuth2TokenCustomizer<OAuth2TokenClaimsContext>> tokenCustomizers = new ArrayList<>();
-			tokenCustomizers.add(defaultTokenCustomizer);
-			tokenCustomizers.add(userTokenCustomizer);
-			tokenCustomizer = new DelegatingOAuth2TokenCustomizer<>(tokenCustomizers);
-		} else {
-			tokenCustomizer = defaultTokenCustomizer;
-		}
-
-		return tokenCustomizer;
+		return getOptionalBean(httpSecurity, type);
 	}
 	}
 
 
 	static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {
 	static AuthorizationServerSettings getAuthorizationServerSettings(HttpSecurity httpSecurity) {

+ 0 - 82
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeTokenCustomizers.java

@@ -1,82 +0,0 @@
-/*
- * Copyright 2020-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.springframework.security.core.Authentication;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CompositeAuthenticationToken;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
-import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
-import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
-import org.springframework.util.CollectionUtils;
-
-/**
- * @author Steve Riesenberg
- * @since 1.3
- */
-final class OAuth2TokenExchangeTokenCustomizers {
-
-	private OAuth2TokenExchangeTokenCustomizers() {
-	}
-
-	static OAuth2TokenCustomizer<JwtEncodingContext> jwt() {
-		return (context) -> context.getClaims().claims((claims) -> customize(context, claims));
-	}
-
-	static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessToken() {
-		return (context) -> context.getClaims().claims((claims) -> customize(context, claims));
-	}
-
-	private static void customize(OAuth2TokenContext context, Map<String, Object> claims) {
-		if (!AuthorizationGrantType.TOKEN_EXCHANGE.equals(context.getAuthorizationGrantType())) {
-			return;
-		}
-
-		if (context.getAuthorizationGrant() instanceof OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication) {
-			// Customize the token claims when audience is present in the request
-			List<String> audience = tokenExchangeAuthentication.getAudiences();
-			if (!CollectionUtils.isEmpty(audience)) {
-				claims.put(OAuth2TokenClaimNames.AUD, audience);
-			}
-		}
-
-		// As per https://datatracker.ietf.org/doc/html/rfc8693#section-4.1,
-		// we handle a composite principal with an actor by adding an "act"
-		// claim with a "sub" claim of the actor.
-		//
-		// If more than one actor is present, we create a chain of delegation by
-		// nesting "act" claims.
-		if (context.getPrincipal() instanceof OAuth2CompositeAuthenticationToken compositeAuthenticationToken) {
-			Map<String, Object> currentClaims = claims;
-			for (Authentication actorPrincipal : compositeAuthenticationToken.getActors()) {
-				Map<String, Object> actClaim = new HashMap<>();
-				actClaim.put("sub", actorPrincipal.getName());
-				currentClaims.put("act", Collections.unmodifiableMap(actClaim));
-				currentClaims = actClaim;
-			}
-		}
-	}
-
-}

+ 6 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java

@@ -27,8 +27,8 @@ import org.springframework.security.jackson2.SecurityJackson2Modules;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ActorAuthenticationToken;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CompositeAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
 
 
 /**
 /**
@@ -39,7 +39,7 @@ import org.springframework.security.oauth2.server.authorization.settings.OAuth2T
  * <li>{@link UnmodifiableMapMixin}</li>
  * <li>{@link UnmodifiableMapMixin}</li>
  * <li>{@link HashSetMixin}</li>
  * <li>{@link HashSetMixin}</li>
  * <li>{@link OAuth2AuthorizationRequestMixin}</li>
  * <li>{@link OAuth2AuthorizationRequestMixin}</li>
- * <li>{@link OAuth2CompositeAuthenticationTokenMixin}</li>
+ * <li>{@link OAuth2TokenExchangeCompositeAuthenticationTokenMixin}</li>
  * <li>{@link DurationMixin}</li>
  * <li>{@link DurationMixin}</li>
  * <li>{@link JwsAlgorithmMixin}</li>
  * <li>{@link JwsAlgorithmMixin}</li>
  * <li>{@link OAuth2TokenFormatMixin}</li>
  * <li>{@link OAuth2TokenFormatMixin}</li>
@@ -80,9 +80,10 @@ public class OAuth2AuthorizationServerJackson2Module extends SimpleModule {
 				UnmodifiableMapMixin.class);
 				UnmodifiableMapMixin.class);
 		context.setMixInAnnotations(HashSet.class, HashSetMixin.class);
 		context.setMixInAnnotations(HashSet.class, HashSetMixin.class);
 		context.setMixInAnnotations(LinkedHashSet.class, HashSetMixin.class);
 		context.setMixInAnnotations(LinkedHashSet.class, HashSetMixin.class);
-		context.setMixInAnnotations(OAuth2ActorAuthenticationToken.class, OAuth2ActorAuthenticationTokenMixin.class);
+		context.setMixInAnnotations(OAuth2TokenExchangeActor.class, OAuth2TokenExchangeActorMixin.class);
 		context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
 		context.setMixInAnnotations(OAuth2AuthorizationRequest.class, OAuth2AuthorizationRequestMixin.class);
-		context.setMixInAnnotations(OAuth2CompositeAuthenticationToken.class, OAuth2CompositeAuthenticationTokenMixin.class);
+		context.setMixInAnnotations(OAuth2TokenExchangeCompositeAuthenticationToken.class,
+				OAuth2TokenExchangeCompositeAuthenticationTokenMixin.class);
 		context.setMixInAnnotations(Duration.class, DurationMixin.class);
 		context.setMixInAnnotations(Duration.class, DurationMixin.class);
 		context.setMixInAnnotations(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
 		context.setMixInAnnotations(SignatureAlgorithm.class, JwsAlgorithmMixin.class);
 		context.setMixInAnnotations(MacAlgorithm.class, JwsAlgorithmMixin.class);
 		context.setMixInAnnotations(MacAlgorithm.class, JwsAlgorithmMixin.class);

+ 7 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2ActorAuthenticationTokenMixin.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java

@@ -16,29 +16,31 @@
 
 
 package org.springframework.security.oauth2.server.authorization.jackson2;
 package org.springframework.security.oauth2.server.authorization.jackson2;
 
 
+import java.util.Map;
+
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ActorAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
 
 
 /**
 /**
- * This mixin class is used to serialize/deserialize {@link OAuth2ActorAuthenticationToken}.
+ * This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeActor}.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
  * @since 1.3
  * @since 1.3
- * @see OAuth2ActorAuthenticationToken
+ * @see OAuth2TokenExchangeActor
  */
  */
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
 		isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
 		isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonIgnoreProperties(ignoreUnknown = true)
-abstract class OAuth2ActorAuthenticationTokenMixin {
+abstract class OAuth2TokenExchangeActorMixin {
 
 
 	@JsonCreator
 	@JsonCreator
-	OAuth2ActorAuthenticationTokenMixin(@JsonProperty("name") String name) {
+	OAuth2TokenExchangeActorMixin(@JsonProperty("claims") Map<String, Object> claims) {
 	}
 	}
 
 
 }
 }

+ 5 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2CompositeAuthenticationTokenMixin.java → oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java

@@ -25,23 +25,23 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 
 
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.oauth2.server.authorization.authentication.OAuth2CompositeAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
 
 
 /**
 /**
- * This mixin class is used to serialize/deserialize {@link OAuth2CompositeAuthenticationToken}.
+ * This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
  *
  *
  * @author Steve Riesenberg
  * @author Steve Riesenberg
  * @since 1.3
  * @since 1.3
- * @see OAuth2CompositeAuthenticationToken
+ * @see OAuth2TokenExchangeCompositeAuthenticationToken
  */
  */
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
 		isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
 		isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
 @JsonIgnoreProperties(ignoreUnknown = true)
 @JsonIgnoreProperties(ignoreUnknown = true)
-abstract class OAuth2CompositeAuthenticationTokenMixin {
+abstract class OAuth2TokenExchangeCompositeAuthenticationTokenMixin {
 
 
 	@JsonCreator
 	@JsonCreator
-	OAuth2CompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
+	OAuth2TokenExchangeCompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
 			@JsonProperty("actors") List<Authentication> actors) {
 			@JsonProperty("actors") List<Authentication> actors) {
 	}
 	}
 
 

+ 24 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java

@@ -16,10 +16,14 @@
 package org.springframework.security.oauth2.server.authorization.oidc.authentication;
 package org.springframework.security.oauth2.server.authorization.oidc.authentication;
 
 
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.oauth2.core.ClaimAccessor;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2RefreshToken;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.core.OAuth2Token;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
 
 
 /**
 /**
  * Utility methods for the OpenID Connect 1.0 {@link AuthenticationProvider}'s.
  * Utility methods for the OpenID Connect 1.0 {@link AuthenticationProvider}'s.
@@ -60,4 +64,24 @@ final class OidcAuthenticationProviderUtils {
 
 
 		return authorizationBuilder.build();
 		return authorizationBuilder.build();
 	}
 	}
+
+	static <T extends OAuth2Token> OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token,
+			OAuth2TokenContext accessTokenContext) {
+
+		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+				token.getTokenValue(), token.getIssuedAt(), token.getExpiresAt(),
+				accessTokenContext.getAuthorizedScopes());
+		OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient().getTokenSettings()
+				.getAccessTokenFormat();
+		builder.token(accessToken, (metadata) -> {
+			if (token instanceof ClaimAccessor claimAccessor) {
+				metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims());
+			}
+			metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
+			metadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue());
+		});
+
+		return accessToken;
+	}
+
 }
 }

+ 2 - 11
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

@@ -34,7 +34,6 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.factory.PasswordEncoderFactories;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClaimAccessor;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@@ -285,22 +284,14 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 			this.logger.trace("Generated registration access token");
 			this.logger.trace("Generated registration access token");
 		}
 		}
 
 
-		OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
-				registrationAccessToken.getTokenValue(), registrationAccessToken.getIssuedAt(),
-				registrationAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
-
 		// @formatter:off
 		// @formatter:off
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 		OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
 				.principalName(registeredClient.getClientId())
 				.principalName(registeredClient.getClientId())
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
 				.authorizedScopes(authorizedScopes);
 				.authorizedScopes(authorizedScopes);
 		// @formatter:on
 		// @formatter:on
-		if (registrationAccessToken instanceof ClaimAccessor) {
-			authorizationBuilder.token(accessToken, (metadata) ->
-					metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) registrationAccessToken).getClaims()));
-		} else {
-			authorizationBuilder.accessToken(accessToken);
-		}
+
+		OidcAuthenticationProviderUtils.accessToken(authorizationBuilder, registrationAccessToken, tokenContext);
 
 
 		OAuth2Authorization authorization = authorizationBuilder.build();
 		OAuth2Authorization authorization = authorizationBuilder.build();
 
 

+ 18 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java

@@ -18,7 +18,9 @@ package org.springframework.security.oauth2.server.authorization.token;
 import java.security.MessageDigest;
 import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
 import java.security.cert.X509Certificate;
 import java.util.Base64;
 import java.util.Base64;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Consumer;
 
 
@@ -28,6 +30,8 @@ import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
 
 
 /**
 /**
  * @author Joe Grandja
  * @author Joe Grandja
@@ -64,6 +68,20 @@ final class DefaultOAuth2TokenClaimsConsumer implements Consumer<Map<String, Obj
 				}
 				}
 			}
 			}
 		}
 		}
+
+		// Add 'act' claim for delegation use case of Token Exchange Grant.
+		// If more than one actor is present, we create a chain of delegation by nesting "act" claims.
+		if (this.context.getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
+			Map<String, Object> currentClaims = claims;
+			for (OAuth2TokenExchangeActor actor : compositeAuthenticationToken.getActors()) {
+				Map<String, Object> actorClaims = actor.getClaims();
+				Map<String, Object> actClaim = new LinkedHashMap<>();
+				actClaim.put(OAuth2TokenClaimNames.ISS, actorClaims.get(OAuth2TokenClaimNames.ISS));
+				actClaim.put(OAuth2TokenClaimNames.SUB, actorClaims.get(OAuth2TokenClaimNames.SUB));
+				currentClaims.put("act", Collections.unmodifiableMap(actClaim));
+				currentClaims = actClaim;
+			}
+		}
 	}
 	}
 
 
 	private static String computeSHA256Thumbprint(X509Certificate x509Certificate) throws Exception {
 	private static String computeSHA256Thumbprint(X509Certificate x509Certificate) throws Exception {

+ 5 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java

@@ -21,6 +21,7 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 import java.util.Set;
 import java.util.Set;
@@ -194,8 +195,9 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
 			}
 			}
 		});
 		});
 
 
-		return new OAuth2TokenExchangeAuthenticationToken(resources, audiences, requestedScopes, requestedTokenType,
-			subjectToken, subjectTokenType, actorToken, actorTokenType, clientPrincipal, additionalParameters);
+		return new OAuth2TokenExchangeAuthenticationToken(requestedTokenType, subjectToken, subjectTokenType,
+				clientPrincipal, actorToken, actorTokenType, new LinkedHashSet<>(resources),
+				new LinkedHashSet<>(audiences), requestedScopes, additionalParameters);
 	}
 	}
 
 
 	private static void validateTokenType(String parameterName, String tokenTypeValue) {
 	private static void validateTokenType(String parameterName, String tokenTypeValue) {
@@ -221,4 +223,5 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
 			return false;
 			return false;
 		}
 		}
 	}
 	}
+
 }
 }