Add tests for Token Exchange
Issue gh-60
This commit is contained in:
@@ -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.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -92,15 +93,17 @@ public class TestOAuth2Authorizations {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
builder
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(accessTokenClaims)))
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(registeredClient, accessTokenClaims)))
|
||||
.refreshToken(refreshToken);
|
||||
}
|
||||
|
||||
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<>();
|
||||
OAuth2TokenFormat accessTokenFormat = registeredClient.getTokenSettings().getAccessTokenFormat();
|
||||
tokenMetadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue());
|
||||
tokenMetadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
|
||||
if (CollectionUtils.isEmpty(tokenClaims)) {
|
||||
tokenClaims = defaultTokenClaims();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,11 +21,10 @@ import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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.OAuth2ClientCredentialsAuthenticationToken;
|
||||
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.TestRegisteredClients;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
@@ -84,6 +84,7 @@ import static org.mockito.Mockito.when;
|
||||
public class OAuth2TokenEndpointFilterTests {
|
||||
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
|
||||
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 OAuth2TokenEndpointFilter filter;
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
@@ -453,6 +454,70 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
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
|
||||
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
@@ -648,4 +713,21 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user