9 changed files with 1871 additions and 4 deletions
@ -0,0 +1,48 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,336 @@
@@ -0,0 +1,336 @@
|
||||
/* |
||||
* Copyright 2020-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.security.oauth2.server.authorization.web.authentication; |
||||
|
||||
import org.junit.jupiter.api.AfterEach; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||
import org.springframework.security.core.Authentication; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.security.core.context.SecurityContextImpl; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; |
||||
import org.springframework.security.oauth2.core.OAuth2Error; |
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; |
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; |
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link OAuth2TokenExchangeAuthenticationConverter}. |
||||
* |
||||
* @author Steve Riesenberg |
||||
*/ |
||||
public class OAuth2TokenExchangeAuthenticationConverterTests { |
||||
private static final String CLIENT_ID = "client-1"; |
||||
private static final String TOKEN_URI = "/oauth2/token"; |
||||
private static final String SUBJECT_TOKEN = "EfYu_0jEL"; |
||||
private static final String ACTOR_TOKEN = "JlNE_xR1f"; |
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; |
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; |
||||
|
||||
private OAuth2TokenExchangeAuthenticationConverter converter; |
||||
|
||||
@BeforeEach |
||||
public void setUp() { |
||||
this.converter = new OAuth2TokenExchangeAuthenticationConverter(); |
||||
} |
||||
|
||||
@AfterEach |
||||
public void tearDown() { |
||||
SecurityContextHolder.clearContext(); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMissingGrantTypeThenReturnNull() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
Authentication authentication = this.converter.convert(request); |
||||
assertThat(authentication).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenInvalidResourceThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.RESOURCE, "invalid"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.RESOURCE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenResourceContainsFragmentThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/#fragment"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.RESOURCE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMultipleScopeParametersThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SCOPE, "one"); |
||||
request.addParameter(OAuth2ParameterNames.SCOPE, "two"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SCOPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMultipleRequestedTokenTypeParametersThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenInvalidRequestedTokenTypeThenUnsupportedTokenTypeError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, "invalid"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMissingSubjectTokenThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMultipleSubjectTokenParametersThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, "another"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMissingSubjectTokenTypeThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenMultipleSubjectTokenTypeParametersThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenInvalidSubjectTokenTypeThenUnsupportedTokenTypeError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, "invalid"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void convertWhenMultipleActorTokenParametersThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, "another"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenActorTokenAndMissingActorTokenTypeThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenActorTokenTypeAndMissingActorTokenThenInvalidRequestError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenInvalidActorTokenTypeThenUnsupportedTokenTypeError() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, "invalid"); |
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class) |
||||
.isThrownBy(() -> this.converter.convert(request)) |
||||
.withMessageContaining(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) |
||||
.extracting(OAuth2AuthenticationException::getError) |
||||
.extracting(OAuth2Error::getErrorCode) |
||||
.isEqualTo(OAuth2ErrorCodes.UNSUPPORTED_TOKEN_TYPE); |
||||
// @formatter:on
|
||||
} |
||||
|
||||
@Test |
||||
public void convertWhenAllParametersThenTokenExchangeAuthenticationToken() { |
||||
MockHttpServletRequest request = createRequest(); |
||||
request.addParameter(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); |
||||
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource1"); |
||||
request.addParameter(OAuth2ParameterNames.RESOURCE, "https://mydomain.com/resource2"); |
||||
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience1"); |
||||
request.addParameter(OAuth2ParameterNames.AUDIENCE, "audience2"); |
||||
request.addParameter(OAuth2ParameterNames.SCOPE, "one two"); |
||||
request.addParameter(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); |
||||
request.addParameter(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); |
||||
|
||||
SecurityContextImpl securityContext = new SecurityContextImpl(); |
||||
securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null)); |
||||
SecurityContextHolder.setContext(securityContext); |
||||
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = |
||||
(OAuth2TokenExchangeAuthenticationToken) this.converter.convert(request); |
||||
assertThat(authentication).isNotNull(); |
||||
assertThat(authentication.getResources()).containsExactly("https://mydomain.com/resource1", |
||||
"https://mydomain.com/resource2"); |
||||
assertThat(authentication.getAudiences()).containsExactly("audience1", "audience2"); |
||||
assertThat(authentication.getScopes()).containsExactly("one", "two"); |
||||
assertThat(authentication.getRequestedTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||
assertThat(authentication.getSubjectToken()).isEqualTo(SUBJECT_TOKEN); |
||||
assertThat(authentication.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE_VALUE); |
||||
assertThat(authentication.getActorToken()).isEqualTo(ACTOR_TOKEN); |
||||
assertThat(authentication.getActorTokenType()).isEqualTo(JWT_TOKEN_TYPE_VALUE); |
||||
} |
||||
|
||||
private static MockHttpServletRequest createRequest() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setMethod(HttpMethod.POST.name()); |
||||
request.setRequestURI(TOKEN_URI); |
||||
return request; |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue