Browse Source

Token Exchange review updates

Issue gh-60
pull/1609/head
Steve Riesenberg 2 years ago
parent
commit
2f1f45bc01
No known key found for this signature in database
GPG Key ID: 3D0169B18AB8F0A9
  1. 8
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/aot/hint/OAuth2AuthorizationServerBeanRegistrationAotProcessor.java
  2. 53
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ActorAuthenticationToken.java
  3. 24
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthenticationProviderUtils.java
  4. 12
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  5. 14
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java
  6. 12
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2DeviceCodeAuthenticationProvider.java
  7. 14
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java
  8. 70
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActor.java
  9. 93
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java
  10. 89
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationToken.java
  11. 25
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationToken.java
  12. 46
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/DelegatingOAuth2TokenCustomizer.java
  13. 32
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java
  14. 82
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeTokenCustomizers.java
  15. 11
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2AuthorizationServerJackson2Module.java
  16. 12
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeActorMixin.java
  17. 10
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/jackson2/OAuth2TokenExchangeCompositeAuthenticationTokenMixin.java
  18. 24
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcAuthenticationProviderUtils.java
  19. 13
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  20. 18
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumer.java
  21. 7
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java

8
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; @@ -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.user.DefaultOAuth2User;
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.settings.OAuth2TokenFormat;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
@ -111,9 +111,9 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis @@ -111,9 +111,9 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
TypeReference.of(OidcIdToken.class),
TypeReference.of(AbstractOAuth2Token.class),
TypeReference.of(OidcUserInfo.class),
TypeReference.of(OAuth2ActorAuthenticationToken.class),
TypeReference.of(OAuth2TokenExchangeActor.class),
TypeReference.of(OAuth2AuthorizationRequest.class),
TypeReference.of(OAuth2CompositeAuthenticationToken.class),
TypeReference.of(OAuth2TokenExchangeCompositeAuthenticationToken.class),
TypeReference.of(AuthorizationGrantType.class),
TypeReference.of(OAuth2AuthorizationResponseType.class),
TypeReference.of(OAuth2TokenFormat.class)

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

@ -1,53 +0,0 @@ @@ -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
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; @@ -17,12 +17,16 @@ package org.springframework.security.oauth2.server.authorization.authentication;
import org.springframework.security.authentication.AuthenticationProvider;
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.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
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.
@ -74,4 +78,24 @@ final class OAuth2AuthenticationProviderUtils { @@ -74,4 +78,24 @@ final class OAuth2AuthenticationProviderUtils {
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;
}
}

12
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; @@ -37,7 +37,6 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@ -204,15 +203,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth @@ -204,15 +203,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
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 -----
OAuth2RefreshToken refreshToken = null;

14
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; @@ -27,7 +27,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@ -137,22 +136,15 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth @@ -137,22 +136,15 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
this.logger.trace("Generated access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(clientPrincipal.getName())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizedScopes(authorizedScopes);
// @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();

12
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; @@ -25,7 +25,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2DeviceCode;
@ -213,15 +212,8 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat @@ -213,15 +212,8 @@ public final class OAuth2DeviceCodeAuthenticationProvider implements Authenticat
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 -----
OAuth2RefreshToken refreshToken = null;

14
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; @@ -29,7 +29,6 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
@ -181,17 +180,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic @@ -181,17 +180,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
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 -----
OAuth2RefreshToken currentRefreshToken = refreshToken.getToken();

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

@ -0,0 +1,70 @@ @@ -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);
}
}

93
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProvider.java

@ -22,6 +22,7 @@ import java.util.LinkedHashSet; @@ -22,6 +22,7 @@ import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.logging.Log;
@ -31,15 +32,12 @@ import org.springframework.security.authentication.AuthenticationProvider; @@ -31,15 +32,12 @@ import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
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.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2Token;
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.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
@ -47,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere @@ -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.settings.OAuth2TokenFormat;
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.OAuth2TokenGenerator;
import org.springframework.util.Assert;
@ -73,6 +72,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -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 ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
private static final String MAY_ACT = "may_act";
private final Log logger = LogFactory.getLog(getClass());
@ -136,11 +137,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -136,11 +137,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
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) {
@ -153,11 +151,11 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -153,11 +151,11 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
// 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
// become the actor and act on behalf of another party.
String authorizedActorSubject = null;
Map<String, Object> authorizedActorClaims = null;
if (subjectToken.getClaims() != null &&
subjectToken.getClaims().containsKey(MAY_ACT) &&
subjectToken.getClaims().get(MAY_ACT) instanceof Map<?, ?> mayAct) {
authorizedActorSubject = (String) mayAct.get(StandardClaimNames.SUB);
authorizedActorClaims = (Map<String, Object>) mayAct;
}
OAuth2Authorization actorAuthorization = null;
@ -181,19 +179,15 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -181,19 +179,15 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
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);
}
@ -235,10 +229,6 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -235,10 +229,6 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
this.logger.trace("Generated access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(subjectAuthorization.getPrincipalName())
@ -247,14 +237,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -247,14 +237,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
.attribute(Principal.class.getName(), principal);
// @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();
this.authorizationService.save(authorization);
@ -274,6 +258,13 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -274,6 +258,13 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
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) {
for (String requestedScope : requestedScopes) {
if (!registeredClient.getScopes().contains(requestedScope)) {
@ -284,26 +275,40 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti @@ -284,26 +275,40 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
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) {
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();
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

89
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; @@ -17,7 +17,7 @@ package org.springframework.security.oauth2.server.authorization.authentication;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@ -36,10 +36,6 @@ import org.springframework.util.Assert; @@ -36,10 +36,6 @@ import org.springframework.util.Assert;
*/
public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
private final List<String> resources;
private final List<String> audiences;
private final String requestedTokenType;
private final String subjectToken;
@ -50,70 +46,47 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG @@ -50,70 +46,47 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
private final String actorTokenType;
private final Set<String> resources;
private final Set<String> audiences;
private final Set<String> scopes;
/**
* 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 subjectToken the subject token
* @param subjectTokenType the subject token type
* @param clientPrincipal the authenticated client principal
* @param actorToken the actor token
* @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
*/
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);
Assert.notNull(resources, "resources cannot be null");
Assert.notNull(audiences, "audiences cannot be null");
Assert.hasText(requestedTokenType, "requestedTokenType cannot be empty");
Assert.hasText(subjectToken, "subjectToken cannot be empty");
Assert.hasText(subjectTokenType, "subjectTokenType cannot be empty");
this.resources = resources;
this.audiences = audiences;
this.requestedTokenType = requestedTokenType;
this.subjectToken = subjectToken;
this.subjectTokenType = subjectTokenType;
this.actorToken = actorToken;
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(
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.
*
@ -158,4 +131,32 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG @@ -158,4 +131,32 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
public String getActorTokenType() {
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;
}
}

25
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 @@ @@ -16,10 +16,10 @@
package org.springframework.security.oauth2.server.authorization.authentication;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.Authentication;
@ -33,13 +33,13 @@ import org.springframework.util.Assert; @@ -33,13 +33,13 @@ import org.springframework.util.Assert;
* @since 1.3
* @see OAuth2TokenExchangeAuthenticationToken
*/
public class OAuth2CompositeAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAuthenticationToken {
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);
Assert.notNull(subject, "subject cannot be null");
Assert.notNull(actors, "actors cannot be null");
@ -63,7 +63,22 @@ public class OAuth2CompositeAuthenticationToken extends AbstractAuthenticationTo @@ -63,7 +63,22 @@ public class OAuth2CompositeAuthenticationToken extends AbstractAuthenticationTo
return this.subject;
}
public List<Authentication> getActors() {
public List<OAuth2TokenExchangeActor> getActors() {
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);
}
}

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

@ -1,46 +0,0 @@ @@ -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);
}
}
}

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

@ -15,8 +15,6 @@ @@ -15,8 +15,6 @@
*/
package org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource;
@ -172,39 +170,13 @@ final class OAuth2ConfigurerUtils { @@ -172,39 +170,13 @@ final class OAuth2ConfigurerUtils {
}
private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
OAuth2TokenCustomizer<JwtEncodingContext> defaultTokenCustomizer = OAuth2TokenExchangeTokenCustomizers.jwt();
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) {
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> defaultTokenCustomizer = OAuth2TokenExchangeTokenCustomizers.accessToken();
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) {

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

@ -1,82 +0,0 @@ @@ -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;
}
}
}
}

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

12
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 @@ @@ -16,29 +16,31 @@
package org.springframework.security.oauth2.server.authorization.jackson2;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
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
* @since 1.3
* @see OAuth2ActorAuthenticationToken
* @see OAuth2TokenExchangeActor
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class OAuth2ActorAuthenticationTokenMixin {
abstract class OAuth2TokenExchangeActorMixin {
@JsonCreator
OAuth2ActorAuthenticationTokenMixin(@JsonProperty("name") String name) {
OAuth2TokenExchangeActorMixin(@JsonProperty("claims") Map<String, Object> claims) {
}
}

10
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; @@ -25,23 +25,23 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
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
* @since 1.3
* @see OAuth2CompositeAuthenticationToken
* @see OAuth2TokenExchangeCompositeAuthenticationToken
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class OAuth2CompositeAuthenticationTokenMixin {
abstract class OAuth2TokenExchangeCompositeAuthenticationTokenMixin {
@JsonCreator
OAuth2CompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
OAuth2TokenExchangeCompositeAuthenticationTokenMixin(@JsonProperty("subject") Authentication subject,
@JsonProperty("actors") List<Authentication> actors) {
}

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

@ -16,10 +16,14 @@ @@ -16,10 +16,14 @@
package org.springframework.security.oauth2.server.authorization.oidc.authentication;
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.OAuth2Token;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
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.
@ -60,4 +64,24 @@ final class OidcAuthenticationProviderUtils { @@ -60,4 +64,24 @@ final class OidcAuthenticationProviderUtils {
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;
}
}

13
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; @@ -34,7 +34,6 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
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.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
@ -285,22 +284,14 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe @@ -285,22 +284,14 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
this.logger.trace("Generated registration access token");
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
registrationAccessToken.getTokenValue(), registrationAccessToken.getIssuedAt(),
registrationAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
// @formatter:off
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
.principalName(registeredClient.getClientId())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizedScopes(authorizedScopes);
// @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();

18
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; @@ -18,7 +18,9 @@ package org.springframework.security.oauth2.server.authorization.token;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
@ -28,6 +30,8 @@ import org.springframework.security.oauth2.core.OAuth2Error; @@ -28,6 +30,8 @@ import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
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.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
/**
* @author Joe Grandja
@ -64,6 +68,20 @@ final class DefaultOAuth2TokenClaimsConsumer implements Consumer<Map<String, Obj @@ -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 {

7
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverter.java

@ -21,6 +21,7 @@ import java.util.Arrays; @@ -21,6 +21,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -194,8 +195,9 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent @@ -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) {
@ -221,4 +223,5 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent @@ -221,4 +223,5 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
return false;
}
}
}

Loading…
Cancel
Save