Browse Source

Add tests for Token Exchange

Issue gh-60
pull/1609/head
Steve Riesenberg 2 years ago
parent
commit
ef859a3a51
No known key found for this signature in database
GPG Key ID: 3D0169B18AB8F0A9
  1. 7
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java
  2. 48
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java
  3. 702
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java
  4. 134
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java
  5. 66
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java
  6. 368
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java
  7. 128
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumerTests.java
  8. 86
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java
  9. 336
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java

7
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/TestOAuth2Authorizations.java

@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
/** /**
@ -92,15 +93,17 @@ public class TestOAuth2Authorizations {
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken( OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS)); "refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
builder builder
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(accessTokenClaims))) .token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(registeredClient, accessTokenClaims)))
.refreshToken(refreshToken); .refreshToken(refreshToken);
} }
return builder; return builder;
} }
private static Map<String, Object> tokenMetadata(Map<String, Object> tokenClaims) { private static Map<String, Object> tokenMetadata(RegisteredClient registeredClient, Map<String, Object> tokenClaims) {
Map<String, Object> tokenMetadata = new HashMap<>(); Map<String, Object> tokenMetadata = new HashMap<>();
OAuth2TokenFormat accessTokenFormat = registeredClient.getTokenSettings().getAccessTokenFormat();
tokenMetadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue());
tokenMetadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false); tokenMetadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
if (CollectionUtils.isEmpty(tokenClaims)) { if (CollectionUtils.isEmpty(tokenClaims)) {
tokenClaims = defaultTokenClaims(); tokenClaims = defaultTokenClaims();

48
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeActorTests.java

@ -0,0 +1,48 @@
/*
* 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.Map;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link OAuth2TokenExchangeActor}.
*
* @author Steve Riesenberg
*/
public class OAuth2TokenExchangeActorTests {
@Test
public void constructorWhenClaimsNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new OAuth2TokenExchangeActor(null))
.withMessage("claims cannot be null");
// @formatter:on
}
@Test
public void constructorWhenRequiredParametersThenCreated() {
Map<String, Object> claims = Map.of("claim1", "value1");
OAuth2TokenExchangeActor actor = new OAuth2TokenExchangeActor(claims);
assertThat(actor.getClaims()).isEqualTo(claims);
}
}

702
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationProviderTests.java

@ -0,0 +1,702 @@
/*
* 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.security.Principal;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
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.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
/**
* Tests for {@link OAuth2TokenExchangeAuthenticationProvider}.
*
* @author Steve Riesenberg
*/
public class OAuth2TokenExchangeAuthenticationProviderTests {
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2");
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2");
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
private static final String ACTOR_TOKEN = "JlNE_xR1f";
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
private OAuth2AuthorizationService authorizationService;
private OAuth2TokenGenerator<OAuth2Token> tokenGenerator;
private OAuth2TokenExchangeAuthenticationProvider authenticationProvider;
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.tokenGenerator = mock(OAuth2TokenGenerator.class);
this.authenticationProvider = new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService,
this.tokenGenerator);
mockAuthorizationServerContext();
}
@AfterEach
public void tearDown() {
AuthorizationServerContextHolder.resetContext();
}
@Test
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(null, this.tokenGenerator))
.withMessage("authorizationService cannot be null");
// @formatter:on
}
@Test
public void constructorWhenTokenGeneratorNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new OAuth2TokenExchangeAuthenticationProvider(this.authorizationService, null))
.withMessage("tokenGenerator cannot be null");
// @formatter:on
}
@Test
public void supportsWhenTypeOAuth2TokenExchangeAuthenticationTokenThenReturnTrue() {
assertThat(this.authenticationProvider.supports(OAuth2TokenExchangeAuthenticationToken.class)).isTrue();
}
@Test
public void authenticateWhenClientNotAuthenticatedThenThrowOAuth2AuthenticationException() {
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken("client-1",
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null, null);
Authentication authentication = new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN,
ACCESS_TOKEN_TYPE_VALUE, clientPrincipal, null, null, RESOURCES, AUDIENCES, null, null);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
// @formatter:on
}
@Test
public void authenticateWhenInvalidGrantTypeThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
// @formatter:on
}
@Test
public void authenticateWhenInvalidRequestedTokenTypeThenThrowOAuth2AuthenticationException() {
// @formatter:off
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
.tokenSettings(TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE).build())
.build();
// @formatter:on
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void authenticateWhenSubjectTokenNotFoundThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(null);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenSubjectTokenNotActiveThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createExpiredAccessToken(SUBJECT_TOKEN)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenSubjectTokenTypeJwtAndSubjectTokenFormatReferenceThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient);
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenSubjectPrincipalNullThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
// @formatter:off
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN))
.attributes((attributes) -> attributes.remove(Principal.class.getName()))
.build();
// @formatter:on
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenActorTokenNotFoundThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, (OAuth2Authorization) null);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenActorTokenNotActiveThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN)).build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createExpiredAccessToken(ACTOR_TOKEN)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenActorTokenTypeJwtAndActorTokenFormatReferenceThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient);
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)).build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(ACTOR_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenMayActAndActorIssClaimNotAuthorizedThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
OAuth2TokenClaimNames.SUB, "actor");
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims)))
.build();
// @formatter:on
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "invalid-issuer",
OAuth2TokenClaimNames.SUB, "actor");
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenMayActAndActorSubClaimNotAuthorizedThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
OAuth2TokenClaimNames.SUB, "actor");
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims)))
.build();
// @formatter:on
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", OAuth2TokenClaimNames.SUB,
"invalid-actor");
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenMayActAndImpersonationThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
OAuth2TokenClaimNames.SUB, "actor");
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims)))
.build();
// @formatter:on
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_GRANT);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenInvalidScopeInRequestThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient,
Set.of("invalid"));
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN)).build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(ACTOR_TOKEN)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenInvalidScopeInSubjectAuthorizationThenThrowOAuth2AuthenticationException() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, Set.of());
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN)).authorizedScopes(Set.of("invalid")).build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(ACTOR_TOKEN)).build();
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_SCOPE);
// @formatter:on
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verifyNoMoreInteractions(this.authorizationService);
verifyNoInteractions(this.tokenGenerator);
}
@Test
public void authenticateWhenNoActorTokenAndValidTokenExchangeThenReturnAccessTokenForImpersonation() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN))
.attribute(Principal.class.getName(), userPrincipal)
.build();
// @formatter:on
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization);
OAuth2AccessToken accessToken = createAccessToken("token-value");
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
OAuth2AccessTokenAuthenticationToken authenticationResult =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
assertThat(authenticationResult.getRefreshToken()).isNull();
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.tokenGenerator).generate(tokenContextCaptor.capture());
verify(this.authorizationService).save(authorizationCaptor.capture());
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator);
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue();
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization);
assertThat(tokenContext.<Authentication>getPrincipal()).isSameAs(userPrincipal);
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull();
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN);
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName());
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isSameAs(userPrincipal);
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken);
assertThat(authorization.getRefreshToken()).isNull();
}
@Test
public void authenticateWhenNoActorTokenAndPreviousActorThenReturnAccessTokenForImpersonation() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
OAuth2TokenExchangeActor previousActor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
OAuth2TokenClaimNames.SUB, "actor"));
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal =
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(previousActor));
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN))
.attribute(Principal.class.getName(), subjectPrincipal)
.build();
// @formatter:on
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization);
OAuth2AccessToken accessToken = createAccessToken("token-value");
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
OAuth2AccessTokenAuthenticationToken authenticationResult =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
assertThat(authenticationResult.getRefreshToken()).isNull();
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.tokenGenerator).generate(tokenContextCaptor.capture());
verify(this.authorizationService).save(authorizationCaptor.capture());
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator);
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue();
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization);
assertThat(tokenContext.<Authentication>getPrincipal()).isSameAs(userPrincipal);
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull();
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN);
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName());
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isSameAs(userPrincipal);
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken);
assertThat(authorization.getRefreshToken()).isNull();
}
@Test
public void authenticateWhenActorTokenAndValidTokenExchangeThenReturnAccessTokenForDelegation() {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
OAuth2TokenClaimNames.SUB, "actor1"));
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2",
OAuth2TokenClaimNames.SUB, "actor2"));
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal =
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(actor1));
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", actor2.getClaims())))
.attribute(Principal.class.getName(), subjectPrincipal)
.build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.principalName(actor2.getSubject())
.token(createAccessToken(ACTOR_TOKEN), withClaims(actor2.getClaims()))
.build();
// @formatter:on
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
subjectAuthorization, actorAuthorization);
OAuth2AccessToken accessToken = createAccessToken("token-value");
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
OAuth2AccessTokenAuthenticationToken authenticationResult =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
assertThat(authenticationResult.getRefreshToken()).isNull();
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
verify(this.authorizationService).findByToken(SUBJECT_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.authorizationService).findByToken(ACTOR_TOKEN, OAuth2TokenType.ACCESS_TOKEN);
verify(this.tokenGenerator).generate(tokenContextCaptor.capture());
verify(this.authorizationService).save(authorizationCaptor.capture());
verifyNoMoreInteractions(this.authorizationService, this.tokenGenerator);
OAuth2TokenContext tokenContext = tokenContextCaptor.getValue();
assertThat(tokenContext.getRegisteredClient()).isEqualTo(registeredClient);
assertThat(tokenContext.getAuthorization()).isEqualTo(subjectAuthorization);
assertThat(tokenContext.getAuthorizationServerContext()).isNotNull();
assertThat(tokenContext.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(tokenContext.getTokenType()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN);
assertThat(tokenContext.<Authentication>getAuthorizationGrant()).isEqualTo(authentication);
assertThat(tokenContext.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
OAuth2TokenExchangeCompositeAuthenticationToken tokenContextPrincipal = tokenContext.getPrincipal();
assertThat(tokenContextPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject());
assertThat(tokenContextPrincipal.getActors()).containsExactly(actor2, actor1);
OAuth2Authorization authorization = authorizationCaptor.getValue();
assertThat(authorization.getPrincipalName()).isEqualTo(subjectAuthorization.getPrincipalName());
assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(authorization.getAuthorizedScopes()).isEqualTo(authentication.getScopes());
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken);
assertThat(authorization.getRefreshToken()).isNull();
OAuth2TokenExchangeCompositeAuthenticationToken authorizationPrincipal =
authorization.getAttribute(Principal.class.getName());
assertThat(authorizationPrincipal).isNotNull();
assertThat(authorizationPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject());
assertThat(authorizationPrincipal.getActors()).containsExactly(actor2, actor1);
}
private static void mockAuthorizationServerContext() {
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build();
TestAuthorizationServerContext authorizationServerContext = new TestAuthorizationServerContext(
authorizationServerSettings, () -> "https://provider.com");
AuthorizationServerContextHolder.setContext(authorizationServerContext);
}
private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient) {
return createDelegationRequest(registeredClient, registeredClient.getScopes());
}
private static OAuth2TokenExchangeAuthenticationToken createDelegationRequest(RegisteredClient registeredClient,
Set<String> requestedScopes) {
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE,
clientPrincipal, ACTOR_TOKEN, ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, requestedScopes, null);
}
private static OAuth2TokenExchangeAuthenticationToken createImpersonationRequest(RegisteredClient registeredClient) {
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE,
clientPrincipal, null, null, RESOURCES, AUDIENCES, registeredClient.getScopes(), null);
}
private static OAuth2TokenExchangeAuthenticationToken createJwtRequest(RegisteredClient registeredClient) {
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, JWT_TOKEN_TYPE_VALUE,
clientPrincipal, ACTOR_TOKEN, JWT_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, registeredClient.getScopes(),
null);
}
private static OAuth2AccessToken createAccessToken(String tokenValue) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt);
}
private static OAuth2AccessToken createExpiredAccessToken(String tokenValue) {
Instant issuedAt = Instant.now().minus(45, ChronoUnit.MINUTES);
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES);
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt);
}
private static Consumer<Map<String, Object>> withClaims(Map<String, Object> claims) {
return (metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claims);
}
private static Consumer<Map<String, Object>> withTokenFormat(OAuth2TokenFormat tokenFormat) {
return (metadata) -> metadata.put(OAuth2TokenFormat.class.getName(), tokenFormat.getValue());
}
}

134
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeAuthenticationTokenTests.java

@ -0,0 +1,134 @@
/*
* 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.Set;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link OAuth2TokenExchangeAuthenticationToken}.
*
* @author Steve Riesenberg
*/
public class OAuth2TokenExchangeAuthenticationTokenTests {
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2");
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2");
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
private static final String ACTOR_TOKEN = "JlNE_xR1f";
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
private Set<String> scopes = Collections.singleton("scope1");
private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
@Test
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, null, null, null, null, null, null, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("clientPrincipal cannot be null");
// @formatter:on
}
@Test
public void constructorWhenRequestedTokenTypeNullOrEmptyThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(null, null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("requestedTokenType cannot be empty");
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken("", null, null, this.clientPrincipal, null, null, null, null, null, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("requestedTokenType cannot be empty");
// @formatter:on
}
@Test
public void constructorWhenSubjectTokenNullOrEmptyThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, null, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("subjectToken cannot be empty");
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, "", null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("subjectToken cannot be empty");
// @formatter:on
}
@Test
public void constructorWhenSubjectTokenTypeNullOrEmptyThenThrowIllegalArgumentException() {
// @formatter:off
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, null, this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("subjectTokenType cannot be empty");
assertThatThrownBy(() -> new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, "", this.clientPrincipal, null, null, null, null, this.scopes, this.additionalParameters))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("subjectTokenType cannot be empty");
// @formatter:on
}
@Test
public void constructorWhenRequiredParametersProvidedThenCreated() {
OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken(
JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, null, null, null,
null, null, this.additionalParameters);
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
assertThat(authentication.getCredentials().toString()).isEmpty();
assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE);
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN);
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE);
assertThat(authentication.getActorToken()).isNull();
assertThat(authentication.getActorTokenType()).isNull();
assertThat(authentication.getResources()).isEmpty();
assertThat(authentication.getAudiences()).isEmpty();
assertThat(authentication.getScopes()).isEmpty();
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters);
}
@Test
public void constructorWhenAllParametersProvidedThenCreated() {
OAuth2TokenExchangeAuthenticationToken authentication = new OAuth2TokenExchangeAuthenticationToken(
JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE, this.clientPrincipal, ACTOR_TOKEN,
ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, this.scopes, this.additionalParameters);
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
assertThat(authentication.getCredentials().toString()).isEmpty();
assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE);
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE);
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN);
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE);
assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN);
assertThat(authentication.getActorTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE);
assertThat(authentication.getResources()).isEqualTo(RESOURCES);
assertThat(authentication.getAudiences()).isEqualTo(AUDIENCES);
assertThat(authentication.getScopes()).isEqualTo(this.scopes);
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters);
}
}

66
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2TokenExchangeCompositeAuthenticationTokenTests.java

@ -0,0 +1,66 @@
/*
* 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.List;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
*
* @author Steve Riesenberg
*/
public class OAuth2TokenExchangeCompositeAuthenticationTokenTests {
@Test
public void constructorWhenSubjectNullThenThrowIllegalArgumentException() {
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(null, null))
.withMessage("subject cannot be null");
// @formatter:on
}
@Test
public void constructorWhenActorsNullThenThrowIllegalArgumentException() {
TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> new OAuth2TokenExchangeCompositeAuthenticationToken(subject, null))
.withMessage("actors cannot be null");
// @formatter:on
}
@Test
public void constructorWhenRequiredParametersProvidedThenCreated() {
TestingAuthenticationToken subject = new TestingAuthenticationToken("subject", null);
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of("claim1", "value1"));
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of("claim2", "value2"));
List<OAuth2TokenExchangeActor> actors = List.of(actor1, actor2);
OAuth2TokenExchangeCompositeAuthenticationToken authentication =
new OAuth2TokenExchangeCompositeAuthenticationToken(subject, actors);
assertThat(authentication.getSubject()).isEqualTo(subject);
assertThat(authentication.getActors()).isEqualTo(actors);
}
}

368
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenExchangeGrantTests.java

@ -0,0 +1,368 @@
/*
* 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.security.Principal;
import java.time.Instant;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.mock.http.client.MockClientHttpResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.test.SpringTestContext;
import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Integration tests for OAuth 2.0 Token Exchange Grant.
*
* @author Steve Riesenberg
*/
@ExtendWith(SpringTestContextExtension.class)
public class OAuth2TokenExchangeGrantTests {
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
private static final String RESOURCE = "https://mydomain.com/resource";
private static final String AUDIENCE = "audience";
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
private static final String ACTOR_TOKEN = "JlNE_xR1f";
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
public final SpringTestContext spring = new SpringTestContext();
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@Autowired
private MockMvc mvc;
@Autowired
private JdbcOperations jdbcOperations;
@Autowired
private RegisteredClientRepository registeredClientRepository;
@Autowired
private OAuth2AuthorizationService authorizationService;
@BeforeAll
public static void init() {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
AuthorizationServerConfiguration.JWK_SOURCE = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
// @formatter:off
AuthorizationServerConfiguration.DB = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql")
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
.build();
// @formatter:on
}
@AfterEach
public void tearDown() {
this.jdbcOperations.update("truncate table oauth2_authorization");
this.jdbcOperations.update("truncate table oauth2_authorization_consent");
this.jdbcOperations.update("truncate table oauth2_registered_client");
}
@AfterAll
public static void destroy() {
AuthorizationServerConfiguration.DB.shutdown();
}
@Test
public void requestWhenAccessTokenRequestNotAuthenticatedThenUnauthorized() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
this.registeredClientRepository.save(registeredClient);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
// @formatter:off
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(parameters))
.andExpect(status().isUnauthorized());
// @formatter:on
}
@Test
public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
this.registeredClientRepository.save(registeredClient);
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user");
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
.attribute(Principal.class.getName(), userPrincipal).build();
this.authorizationService.save(subjectAuthorization);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, subjectAuthorization.getAccessToken().getToken().getTokenValue());
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE);
parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE);
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
// @formatter:off
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.params(parameters)
.headers(withClientAuth(registeredClient)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").doesNotExist())
.andExpect(jsonPath("$.expires_in").isNumber())
.andExpect(jsonPath("$.scope").isNotEmpty())
.andExpect(jsonPath("$.token_type").isNotEmpty())
.andExpect(jsonPath("$.issued_token_type").isNotEmpty())
.andReturn();
// @formatter:on
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
HttpStatus.OK);
OAuth2AccessTokenResponse accessTokenResponse =
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
String accessToken = accessTokenResponse.getAccessToken().getTokenValue();
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken,
OAuth2TokenType.ACCESS_TOKEN);
assertThat(authorization).isNotNull();
assertThat(authorization.getAccessToken()).isNotNull();
assertThat(authorization.getAccessToken().getClaims()).isNotNull();
// We do not populate claims (e.g. `aud`) based on the resource or audience parameters
assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD))
.isEqualTo(List.of(registeredClient.getClientId()));
assertThat(authorization.getRefreshToken()).isNull();
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isEqualTo(userPrincipal);
}
@Test
public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() throws Exception {
this.spring.register(AuthorizationServerConfiguration.class).autowire();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
this.registeredClientRepository.save(registeredClient);
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user");
UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin");
Map<String, Object> actorTokenClaims = new HashMap<>();
actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2");
actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin");
Map<String, Object> subjectTokenClaims = new HashMap<>();
subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1");
subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user");
subjectTokenClaims.put("may_act", actorTokenClaims);
OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN);
OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN);
// @formatter:off
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims)
.id(UUID.randomUUID().toString())
.attribute(Principal.class.getName(), userPrincipal)
.build();
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims)
.id(UUID.randomUUID().toString())
.attribute(Principal.class.getName(), adminPrincipal)
.build();
// @formatter:on
this.authorizationService.save(subjectAuthorization);
this.authorizationService.save(actorAuthorization);
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN);
parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
parameters.set(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
// @formatter:off
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.params(parameters)
.headers(withClientAuth(registeredClient)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.access_token").isNotEmpty())
.andExpect(jsonPath("$.refresh_token").doesNotExist())
.andExpect(jsonPath("$.expires_in").isNumber())
.andExpect(jsonPath("$.scope").isNotEmpty())
.andExpect(jsonPath("$.token_type").isNotEmpty())
.andExpect(jsonPath("$.issued_token_type").isNotEmpty())
.andReturn();
// @formatter:on
MockHttpServletResponse servletResponse = mvcResult.getResponse();
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
HttpStatus.OK);
OAuth2AccessTokenResponse accessTokenResponse =
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
String accessToken = accessTokenResponse.getAccessToken().getTokenValue();
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken,
OAuth2TokenType.ACCESS_TOKEN);
assertThat(authorization).isNotNull();
assertThat(authorization.getAccessToken()).isNotNull();
assertThat(authorization.getAccessToken().getClaims()).isNotNull();
assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull();
assertThat(authorization.getRefreshToken()).isNull();
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName()))
.isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class);
}
private static OAuth2AccessToken createAccessToken(String tokenValue) {
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusSeconds(300);
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt);
}
private static UsernamePasswordAuthenticationToken createUserPrincipal(String username) {
User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER"));
return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities());
}
private static HttpHeaders withClientAuth(RegisteredClient registeredClient) {
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret());
return headers;
}
private static Consumer<Map<String, Object>> withInvalidated() {
return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true);
}
private static Function<OAuth2Authorization.Token<? extends OAuth2Token>, Boolean> isInvalidated() {
return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME);
}
@EnableWebSecurity
@Import(OAuth2AuthorizationServerConfiguration.class)
static class AuthorizationServerConfiguration {
static JWKSource<SecurityContext> JWK_SOURCE;
static EmbeddedDatabase DB;
@Bean
RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
return new JdbcRegisteredClientRepository(jdbcOperations);
}
@Bean
OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
}
@Bean
OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations,
RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository);
}
@Bean
JdbcOperations jdbcOperations() {
return new JdbcTemplate(DB);
}
@Bean
JWKSource<SecurityContext> jwkSource() {
return JWK_SOURCE;
}
@Bean
PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
}

128
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/token/DefaultOAuth2TokenClaimsConsumerTests.java

@ -0,0 +1,128 @@
/*
* 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.token;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Tests for {@link DefaultOAuth2TokenClaimsConsumer}.
*
* @author Steve Riesenberg
*/
public class DefaultOAuth2TokenClaimsConsumerTests {
private OAuth2TokenContext tokenContext;
private Consumer<Map<String, Object>> consumer;
@BeforeEach
public void setUp() {
this.tokenContext = mock(OAuth2TokenContext.class);
this.consumer = new DefaultOAuth2TokenClaimsConsumer(this.tokenContext);
}
@Test
public void acceptWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() {
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.REFRESH_TOKEN);
Map<String, Object> claims = new LinkedHashMap<>();
this.consumer.accept(claims);
assertThat(claims).isEmpty();
}
@Test
public void acceptWhenAuthorizationGrantIsNullThenNoClaimsAdded() {
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
when(this.tokenContext.getAuthorizationGrant()).thenReturn(null);
Map<String, Object> claims = new LinkedHashMap<>();
this.consumer.accept(claims);
assertThat(claims).isEmpty();
}
@Test
public void acceptWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() {
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
OAuth2TokenExchangeAuthenticationToken.class);
when(tokenExchangeAuthentication.getResources()).thenReturn(Set.of("resource1", "resource2"));
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
Map<String, Object> claims = new LinkedHashMap<>();
this.consumer.accept(claims);
// We do not populate claims (e.g. `aud`) based on the resource parameter
assertThat(claims).isEmpty();
}
@Test
public void acceptWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() {
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
OAuth2TokenExchangeAuthenticationToken.class);
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Set.of("audience1", "audience2"));
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
Map<String, Object> claims = new LinkedHashMap<>();
this.consumer.accept(claims);
// NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter
assertThat(claims).isEmpty();
}
@Test
public void acceptWhenTokenExchangeGrantAndDelegationThenActClaimAdded() {
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock(
OAuth2TokenExchangeAuthenticationToken.class);
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Collections.emptySet());
when(this.tokenContext.getTokenType()).thenReturn(OAuth2TokenType.ACCESS_TOKEN);
when(this.tokenContext.getAuthorizationGrant()).thenReturn(tokenExchangeAuthentication);
Authentication subject = new TestingAuthenticationToken("subject", null);
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
OAuth2TokenClaimNames.SUB, "actor1"));
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2",
OAuth2TokenClaimNames.SUB, "actor2"));
OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken(
subject, List.of(actor1, actor2));
when(this.tokenContext.getPrincipal()).thenReturn(principal);
Map<String, Object> claims = new LinkedHashMap<>();
this.consumer.accept(claims);
assertThat(claims).hasSize(1);
assertThat(claims.get("act")).isNotNull();
@SuppressWarnings("unchecked")
Map<String, Object> actClaim1 = (Map<String, Object>) claims.get("act");
assertThat(actClaim1.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer1");
assertThat(actClaim1.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor1");
@SuppressWarnings("unchecked")
Map<String, Object> actClaim2 = (Map<String, Object>) actClaim1.get("act");
assertThat(actClaim2.get(OAuth2TokenClaimNames.ISS)).isEqualTo("issuer2");
assertThat(actClaim2.get(OAuth2TokenClaimNames.SUB)).isEqualTo("actor2");
}
}

86
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java

@ -21,11 +21,10 @@ import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -56,6 +55,7 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
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.OAuth2ClientCredentialsAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
@ -84,6 +84,7 @@ import static org.mockito.Mockito.when;
public class OAuth2TokenEndpointFilterTests { public class OAuth2TokenEndpointFilterTests {
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
private static final String REMOTE_ADDRESS = "remote-address"; private static final String REMOTE_ADDRESS = "remote-address";
private static final String ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
private AuthenticationManager authenticationManager; private AuthenticationManager authenticationManager;
private OAuth2TokenEndpointFilter filter; private OAuth2TokenEndpointFilter filter;
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
@ -453,6 +454,70 @@ public class OAuth2TokenEndpointFilterTests {
assertThat(refreshTokenResult.getTokenValue()).isEqualTo(refreshToken.getTokenValue()); assertThat(refreshTokenResult.getTokenValue()).isEqualTo(refreshToken.getTokenValue());
} }
@Test
public void doFilterWhenTokenExchangeRequestThenAccessTokenResponse() throws Exception {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
OAuth2AccessToken accessToken = new OAuth2AccessToken(
OAuth2AccessToken.TokenType.BEARER, "token",
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
new HashSet<>(Arrays.asList("scope1", "scope2")));
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now());
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
new OAuth2AccessTokenAuthenticationToken(
registeredClient, clientPrincipal, accessToken, refreshToken);
when(this.authenticationManager.authenticate(any())).thenReturn(accessTokenAuthentication);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(clientPrincipal);
SecurityContextHolder.setContext(securityContext);
MockHttpServletRequest request = createTokenExchangeTokenRequest(registeredClient);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class);
this.filter.doFilter(request, response, filterChain);
verifyNoInteractions(filterChain);
ArgumentCaptor<OAuth2TokenExchangeAuthenticationToken> tokenExchangeAuthenticationCaptor =
ArgumentCaptor.forClass(OAuth2TokenExchangeAuthenticationToken.class);
verify(this.authenticationManager).authenticate(tokenExchangeAuthenticationCaptor.capture());
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthenticationToken =
tokenExchangeAuthenticationCaptor.getValue();
assertThat(tokenExchangeAuthenticationToken.getSubjectToken()).isEqualTo("subject-token");
assertThat(tokenExchangeAuthenticationToken.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE);
assertThat(tokenExchangeAuthenticationToken.getPrincipal()).isEqualTo(clientPrincipal);
assertThat(tokenExchangeAuthenticationToken.getScopes()).isEqualTo(registeredClient.getScopes());
assertThat(tokenExchangeAuthenticationToken.getAdditionalParameters())
.containsExactly(entry("custom-param-1", "custom-value-1"),
entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }));
assertThat(tokenExchangeAuthenticationToken.getDetails())
.asInstanceOf(type(WebAuthenticationDetails.class))
.extracting(WebAuthenticationDetails::getRemoteAddress)
.isEqualTo(REMOTE_ADDRESS);
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken();
assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType());
assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue());
assertThat(accessTokenResult.getIssuedAt()).isBetween(
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
assertThat(accessTokenResult.getExpiresAt()).isBetween(
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes());
OAuth2RefreshToken refreshTokenResult = accessTokenResponse.getRefreshToken();
assertThat(refreshTokenResult.getTokenValue()).isEqualTo(refreshToken.getTokenValue());
}
@Test @Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
@ -648,4 +713,21 @@ public class OAuth2TokenEndpointFilterTests {
return request; return request;
} }
private static MockHttpServletRequest createTokenExchangeTokenRequest(RegisteredClient registeredClient) {
String requestUri = DEFAULT_TOKEN_ENDPOINT_URI;
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
request.setServletPath(requestUri);
request.setRemoteAddr(REMOTE_ADDRESS);
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "subject-token");
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE);
request.addParameter(OAuth2ParameterNames.SCOPE,
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
request.addParameter("custom-param-1", "custom-value-1");
request.addParameter("custom-param-2", "custom-value-1", "custom-value-2");
return request;
}
} }

336
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2TokenExchangeAuthenticationConverterTests.java

@ -0,0 +1,336 @@
/*
* 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.web.authentication;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
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.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link OAuth2TokenExchangeAuthenticationConverter}.
*
* @author Steve Riesenberg
*/
public class OAuth2TokenExchangeAuthenticationConverterTests {
private static final String CLIENT_ID = "client-1";
private static final String TOKEN_URI = "/oauth2/token";
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
private static final String ACTOR_TOKEN = "JlNE_xR1f";
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
private OAuth2TokenExchangeAuthenticationConverter converter;
@BeforeEach
public void setUp() {
this.converter = new OAuth2TokenExchangeAuthenticationConverter();
}
@AfterEach
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void convertWhenMissingGrantTypeThenReturnNull() {
MockHttpServletRequest request = createRequest();
Authentication authentication = this.converter.convert(request);
assertThat(authentication).isNull();
}
@Test
public void convertWhenInvalidResourceThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.RESOURCE, "invalid");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.RESOURCE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenResourceContainsFragmentThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/#fragment");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.RESOURCE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenMultipleScopeParametersThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SCOPE, "one");
request.addParameter(OAuth2ParameterNames.SCOPE, "two");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SCOPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenMultipleRequestedTokenTypeParametersThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenInvalidRequestedTokenTypeThenUnsupportedTokenTypeError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, "invalid");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE);
// @formatter:on
}
@Test
public void convertWhenMissingSubjectTokenThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenMultipleSubjectTokenParametersThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "another");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenMissingSubjectTokenTypeThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenMultipleSubjectTokenTypeParametersThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenInvalidSubjectTokenTypeThenUnsupportedTokenTypeError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, "invalid");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE);
// @formatter:on
}
@Test
public void convertWhenMultipleActorTokenParametersThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, "another");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenActorTokenAndMissingActorTokenTypeThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenActorTokenTypeAndMissingActorTokenThenInvalidRequestError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
// @formatter:on
}
@Test
public void convertWhenInvalidActorTokenTypeThenUnsupportedTokenTypeError() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, "invalid");
// @formatter:off
assertThatExceptionOfType(OAuth2AuthenticationException.class)
.isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE)
.extracting(OAuth2AuthenticationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE);
// @formatter:on
}
@Test
public void convertWhenAllParametersThenTokenExchangeAuthenticationToken() {
MockHttpServletRequest request = createRequest();
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource1");
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource2");
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience1");
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience2");
request.addParameter(OAuth2ParameterNames.SCOPE, "one two");
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN);
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN);
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null));
SecurityContextHolder.setContext(securityContext);
OAuth2TokenExchangeAuthenticationToken authentication =
(OAuth2TokenExchangeAuthenticationToken) this.converter.convert(request);
assertThat(authentication).isNotNull();
assertThat(authentication.getResources()).containsExactly("https://mydomain.com/resource1",
"https://mydomain.com/resource2");
assertThat(authentication.getAudiences()).containsExactly("audience1", "audience2");
assertThat(authentication.getScopes()).containsExactly("one", "two");
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE);
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN);
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE);
assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN);
assertThat(authentication.getActorTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE);
}
private static MockHttpServletRequest createRequest() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(HttpMethod.POST.name());
request.setRequestURI(TOKEN_URI);
return request;
}
}
Loading…
Cancel
Save