Polish gh-161
This commit is contained in:
@@ -15,6 +15,52 @@
|
||||
*/
|
||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||
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 static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -22,55 +68,22 @@ import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||
import org.springframework.security.config.test.SpringTestRule;
|
||||
import org.springframework.security.oauth2.core.AbstractOAuth2Token;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
|
||||
import org.springframework.security.oauth2.jose.TestJwks;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenIntrospectionEndpointFilter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.nimbusds.jose.jwk.JWKSet;
|
||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||
import com.nimbusds.jose.proc.SecurityContext;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Integration tests for the OAuth 2.0 Token Introspection endpoint.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionTests {
|
||||
private static RegisteredClientRepository registeredClientRepository;
|
||||
private static OAuth2AuthorizationService authorizationService;
|
||||
private static JWKSource<SecurityContext> jwkSource;
|
||||
private static ProviderSettings providerSettings;
|
||||
private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
|
||||
new OAuth2TokenIntrospectionHttpMessageConverter();
|
||||
|
||||
@Rule
|
||||
public final SpringTestRule spring = new SpringTestRule();
|
||||
@@ -84,6 +97,7 @@ public class OAuth2TokenIntrospectionTests {
|
||||
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
|
||||
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
|
||||
providerSettings = new ProviderSettings().tokenIntrospectionEndpoint("/test/introspect");
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -93,110 +107,101 @@ public class OAuth2TokenIntrospectionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenIntrospectValidRefreshTokenThenActiveResponse() throws Exception {
|
||||
public void requestWhenIntrospectValidAccessTokenThenActive() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2RefreshToken token = authorization.getRefreshToken().getToken();
|
||||
OAuth2TokenType tokenType = OAuth2TokenType.REFRESH_TOKEN;
|
||||
when(authorizationService.findByToken(eq(token.getTokenValue()), isNull())).thenReturn(authorization);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(
|
||||
MockMvcRequestBuilders.post(OAuth2TokenIntrospectionEndpointFilter.DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI)
|
||||
.params(getTokenIntrospectionRequestParameters(token, tokenType))
|
||||
.with(httpBasic(registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.active").value(true))
|
||||
.andExpect(jsonPath("$.client_id").value("client-1"))
|
||||
.andExpect(jsonPath("$.iat").isNotEmpty())
|
||||
.andExpect(jsonPath("$.exp").isNotEmpty())
|
||||
.andExpect(jsonPath("$.username").value("principal"));
|
||||
// @formatter:on
|
||||
|
||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(token.getTokenValue()), isNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenIntrospectValidAccessTokenThenActiveResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build();
|
||||
when(registeredClientRepository.findByClientId(eq(introspectRegisteredClient.getClientId())))
|
||||
.thenReturn(introspectRegisteredClient);
|
||||
|
||||
RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "Scope2")));
|
||||
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN;
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).token(accessToken)
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
JwtClaimsSet tokenClaims = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(authorizedRegisteredClient, accessToken, tokenClaims.getClaims())
|
||||
.build();
|
||||
|
||||
when(authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())).thenReturn(authorization);
|
||||
when(authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
when(registeredClientRepository.findById(eq(authorizedRegisteredClient.getId())))
|
||||
.thenReturn(authorizedRegisteredClient);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(
|
||||
MockMvcRequestBuilders.post(OAuth2TokenIntrospectionEndpointFilter.DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI)
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, tokenType))
|
||||
.with(httpBasic(registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
MvcResult mvcResult = this.mvc.perform(post(providerSettings.tokenIntrospectionEndpoint())
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN))
|
||||
.with(httpBasic(introspectRegisteredClient.getClientId(), introspectRegisteredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.active").value(true))
|
||||
.andExpect(jsonPath("$.client_id").value("client-1"))
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.BEARER.getValue()))
|
||||
.andExpect(jsonPath("$.iat").isNotEmpty())
|
||||
.andExpect(jsonPath("$.exp").isNotEmpty())
|
||||
.andExpect(jsonPath("$.username").value("principal"));
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||
verify(registeredClientRepository).findByClientId(eq(introspectRegisteredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(accessToken.getTokenValue()), isNull());
|
||||
verify(registeredClientRepository).findById(eq(authorizedRegisteredClient.getId()));
|
||||
|
||||
OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult);
|
||||
assertThat(tokenIntrospectionResponse.isActive()).isTrue();
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId());
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(
|
||||
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(
|
||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getScope()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween(
|
||||
tokenClaims.getNotBefore().minusSeconds(1), tokenClaims.getNotBefore().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(tokenClaims.getSubject());
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(tokenClaims.getAudience());
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(tokenClaims.getIssuer());
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenIntrospectTokenIssuedToDifferentClientThenActiveResponse() throws Exception {
|
||||
public void requestWhenIntrospectValidRefreshTokenThenActive() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build();
|
||||
when(registeredClientRepository.findByClientId(eq(introspectRegisteredClient.getClientId())))
|
||||
.thenReturn(introspectRegisteredClient);
|
||||
|
||||
RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().build();
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "Scope2")));
|
||||
OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN;
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient2).token(accessToken)
|
||||
.build();
|
||||
|
||||
when(authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull())).thenReturn(authorization);
|
||||
RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build();
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
when(authorizationService.findByToken(eq(refreshToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
when(registeredClientRepository.findById(eq(authorizedRegisteredClient.getId())))
|
||||
.thenReturn(authorizedRegisteredClient);
|
||||
|
||||
// @formatter:off
|
||||
this.mvc.perform(
|
||||
MockMvcRequestBuilders.post(OAuth2TokenIntrospectionEndpointFilter.DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI)
|
||||
.params(getTokenIntrospectionRequestParameters(accessToken, tokenType))
|
||||
.with(httpBasic(registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
MvcResult mvcResult = this.mvc.perform(post(providerSettings.tokenIntrospectionEndpoint())
|
||||
.params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN))
|
||||
.with(httpBasic(introspectRegisteredClient.getClientId(), introspectRegisteredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.active").value(true))
|
||||
.andExpect(jsonPath("$.client_id").value("client-1"))
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.BEARER.getValue()))
|
||||
.andExpect(jsonPath("$.iat").isNotEmpty())
|
||||
.andExpect(jsonPath("$.exp").isNotEmpty())
|
||||
.andExpect(jsonPath("$.username").value("principal"));
|
||||
.andReturn();
|
||||
// @formatter:on
|
||||
|
||||
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(accessToken.getTokenValue()), isNull());
|
||||
verify(registeredClientRepository).findByClientId(eq(introspectRegisteredClient.getClientId()));
|
||||
verify(authorizationService).findByToken(eq(refreshToken.getTokenValue()), isNull());
|
||||
verify(registeredClientRepository).findById(eq(authorizedRegisteredClient.getId()));
|
||||
|
||||
OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult);
|
||||
assertThat(tokenIntrospectionResponse.isActive()).isTrue();
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId());
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(
|
||||
refreshToken.getIssuedAt().minusSeconds(1), refreshToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(
|
||||
refreshToken.getExpiresAt().minusSeconds(1), refreshToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getScope()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isNull();
|
||||
assertThat(tokenIntrospectionResponse.getId()).isNull();
|
||||
}
|
||||
|
||||
private static MultiValueMap<String, String> getTokenIntrospectionRequestParameters(AbstractOAuth2Token token,
|
||||
@@ -207,6 +212,13 @@ public class OAuth2TokenIntrospectionTests {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception {
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
servletResponse.getContentAsByteArray(), HttpStatus.valueOf(servletResponse.getStatus()));
|
||||
return this.tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfiguration {
|
||||
@@ -225,5 +237,10 @@ public class OAuth2TokenIntrospectionTests {
|
||||
JWKSource<SecurityContext> jwkSource() {
|
||||
return jwkSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
ProviderSettings providerSettings() {
|
||||
return providerSettings;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core.http.converter;
|
||||
|
||||
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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor.ACTIVE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CLIENT_ID;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.TOKEN_TYPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.USERNAME;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.AUD;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.ISS;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.JTI;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.NBF;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.SUB;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionClaimsHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaims;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenIntrospectionClaimsHttpMessageConverter}
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionClaimsHttpMessageConverterTests {
|
||||
private final OAuth2TokenIntrospectionClaimsHttpMessageConverter messageConverter = new OAuth2TokenIntrospectionClaimsHttpMessageConverter();
|
||||
|
||||
@Test
|
||||
public void supportsWhenOidcProviderConfigurationThenTrue() {
|
||||
assertThat(this.messageConverter.supports(OAuth2TokenIntrospectionClaims.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setProviderConfigurationParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.messageConverter.setTokenIntrospectionResponseParametersConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setProviderConfigurationConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.messageConverter.setTokenIntrospectionResponseConverter(null));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void readInternalWhenValidParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String tokenIntrospectionResponseBody = "{\n"
|
||||
+ " \"active\": true,\n"
|
||||
+ " \"iss\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"scope\": \"scope1 Scope2\",\n"
|
||||
+ " \"client_id\": \"clientId1\",\n"
|
||||
+ " \"token_type\": \"Bearer\",\n"
|
||||
+ " \"username\": \"username1\",\n"
|
||||
+ " \"aud\": [\"audience1\", \"audience2\"],\n"
|
||||
+ " \"exp\": 1607637467,\n"
|
||||
+ " \"iat\": 1607633867,\n"
|
||||
+ " \"nbf\": 1607633867,\n"
|
||||
+ " \"sub\": \"subject1\",\n"
|
||||
+ " \"jti\": \"jwtId1\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(
|
||||
tokenIntrospectionResponseBody.getBytes(), HttpStatus.OK);
|
||||
OAuth2TokenIntrospectionClaims tokenIntrospectionResponse = this.messageConverter
|
||||
.readInternal(OAuth2TokenIntrospectionClaims.class, response);
|
||||
Map<String, Object> responseParameters = tokenIntrospectionResponse.getClaims();
|
||||
Condition<Object> collectionContainsCondition = new Condition<>(
|
||||
collection -> Collection.class.isAssignableFrom(collection.getClass())
|
||||
&& ((Collection<String>) collection).contains("audience1")
|
||||
&& ((Collection<String>) collection).contains("audience2"),
|
||||
"collection contains entries");
|
||||
|
||||
// @formatter:off
|
||||
assertThat(responseParameters)
|
||||
.containsEntry(ACTIVE, true)
|
||||
.containsEntry(SCOPE, "scope1 Scope2")
|
||||
.containsEntry(CLIENT_ID, "clientId1")
|
||||
.containsEntry(TOKEN_TYPE, "Bearer")
|
||||
.containsEntry(USERNAME, "username1")
|
||||
.hasEntrySatisfying(AUD, collectionContainsCondition)
|
||||
.containsEntry(EXP, 1607637467L)
|
||||
.containsEntry(IAT, 1607633867L)
|
||||
.containsEntry(NBF, 1607633867L)
|
||||
.containsEntry(ISS, "https://example.com/issuer1")
|
||||
.containsEntry(JTI, "jwtId1")
|
||||
.containsEntry(SUB, "subject1");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenFailingConverterThenThrowException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
this.messageConverter.setTokenIntrospectionResponseConverter(source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
});
|
||||
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2TokenIntrospectionClaims.class, response))
|
||||
.withMessageContaining("An error occurred reading the Token Introspection Response")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenTokenIntrospectionResponseThenSuccess() {
|
||||
// @formatter:off
|
||||
OAuth2TokenIntrospectionClaims providerConfiguration = OAuth2TokenIntrospectionClaims.builder(true)
|
||||
.issuer("https://example.com/issuer1")
|
||||
.scope("scope1 Scope2")
|
||||
.clientId("clientId1")
|
||||
.tokenType(TokenType.BEARER)
|
||||
.username("username1")
|
||||
.audience(Arrays.asList("audience1", "audience2"))
|
||||
.expirationTime(Instant.ofEpochSecond(1607637467))
|
||||
.issuedAt(Instant.ofEpochSecond(1607633867))
|
||||
.notBefore(Instant.ofEpochSecond(1607633867))
|
||||
.jwtId("jwtId1")
|
||||
.subject("subject1").build();
|
||||
// @formatter:on
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
this.messageConverter.writeInternal(providerConfiguration, outputMessage);
|
||||
|
||||
String providerConfigurationResponse = outputMessage.getBodyAsString();
|
||||
// @formatter:off
|
||||
assertThat(providerConfigurationResponse)
|
||||
.contains("\"iss\":\"https://example.com/issuer1\"")
|
||||
.contains("\"active\":true")
|
||||
.contains("\"scope\":\"scope1 Scope2\"")
|
||||
.contains("\"client_id\":\"clientId1\"")
|
||||
.contains("\"token_type\":\"Bearer\"")
|
||||
.contains("\"username\":\"username1\"")
|
||||
.contains("\"aud\":[\"audience1\",\"audience2\"]")
|
||||
.contains("\"exp\":1607637467")
|
||||
.contains("\"iat\":1607633867")
|
||||
.contains("\"nbf\":1607633867")
|
||||
.contains("\"jti\":\"jwtId1\"")
|
||||
.contains("\"sub\":\"subject1\"");
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenWriteFailsThenThrowsException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
Converter<OAuth2TokenIntrospectionClaims, Map<String, Object>> failingConverter = source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
};
|
||||
this.messageConverter.setTokenIntrospectionResponseParametersConverter(failingConverter);
|
||||
|
||||
OAuth2TokenIntrospectionClaims providerConfiguration = OAuth2TokenIntrospectionClaims.builder(true).build();
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
assertThatThrownBy(() -> this.messageConverter.writeInternal(providerConfiguration, outputMessage))
|
||||
.isInstanceOf(HttpMessageNotWritableException.class)
|
||||
.hasMessageContaining("An error occurred writing the Token Introspection Response")
|
||||
.hasMessageContaining(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2020-2021 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.core.http.converter;
|
||||
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken.TokenType;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
|
||||
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.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenIntrospectionHttpMessageConverter}
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionHttpMessageConverterTests {
|
||||
private final OAuth2TokenIntrospectionHttpMessageConverter messageConverter = new OAuth2TokenIntrospectionHttpMessageConverter();
|
||||
|
||||
@Test
|
||||
public void supportsWhenOAuth2TokenIntrospectionThenTrue() {
|
||||
assertThat(this.messageConverter.supports(OAuth2TokenIntrospection.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTokenIntrospectionParametersConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.messageConverter.setTokenIntrospectionParametersConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setTokenIntrospectionConverterWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> this.messageConverter.setTokenIntrospectionConverter(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenValidParametersThenSuccess() throws Exception {
|
||||
// @formatter:off
|
||||
String tokenIntrospectionResponseBody = "{\n"
|
||||
+ " \"active\": true,\n"
|
||||
+ " \"client_id\": \"clientId1\",\n"
|
||||
+ " \"username\": \"username1\",\n"
|
||||
+ " \"iat\": 1607633867,\n"
|
||||
+ " \"exp\": 1607637467,\n"
|
||||
+ " \"scope\": \"scope1 scope2\",\n"
|
||||
+ " \"token_type\": \"Bearer\",\n"
|
||||
+ " \"nbf\": 1607633867,\n"
|
||||
+ " \"sub\": \"subject1\",\n"
|
||||
+ " \"aud\": [\"audience1\", \"audience2\"],\n"
|
||||
+ " \"iss\": \"https://example.com/issuer1\",\n"
|
||||
+ " \"jti\": \"jwtId1\"\n"
|
||||
+ "}\n";
|
||||
// @formatter:on
|
||||
MockClientHttpResponse response = new MockClientHttpResponse(
|
||||
tokenIntrospectionResponseBody.getBytes(), HttpStatus.OK);
|
||||
OAuth2TokenIntrospection tokenIntrospectionResponse = this.messageConverter
|
||||
.readInternal(OAuth2TokenIntrospection.class, response);
|
||||
|
||||
assertThat(tokenIntrospectionResponse.isActive()).isTrue();
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo("clientId1");
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isEqualTo("username1");
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isEqualTo(Instant.ofEpochSecond(1607633867L));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isEqualTo(Instant.ofEpochSecond(1607637467L));
|
||||
assertThat(tokenIntrospectionResponse.getScope()).containsExactlyInAnyOrderElementsOf(Arrays.asList("scope1", "scope2"));
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo("Bearer");
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isEqualTo(Instant.ofEpochSecond(1607633867L));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo("subject1");
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(Arrays.asList("audience1", "audience2"));
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(new URL("https://example.com/issuer1"));
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo("jwtId1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInternalWhenFailingConverterThenThrowException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
this.messageConverter.setTokenIntrospectionConverter(source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
});
|
||||
MockClientHttpResponse response = new MockClientHttpResponse("{}".getBytes(), HttpStatus.OK);
|
||||
|
||||
assertThatExceptionOfType(HttpMessageNotReadableException.class)
|
||||
.isThrownBy(() -> this.messageConverter.readInternal(OAuth2TokenIntrospection.class, response))
|
||||
.withMessageContaining("An error occurred reading the Token Introspection Response")
|
||||
.withMessageContaining(errorMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenTokenIntrospectionThenSuccess() {
|
||||
// @formatter:off
|
||||
OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true)
|
||||
.clientId("clientId1")
|
||||
.username("username1")
|
||||
.issuedAt(Instant.ofEpochSecond(1607633867))
|
||||
.expiresAt(Instant.ofEpochSecond(1607637467))
|
||||
.scope("scope1 scope2")
|
||||
.tokenType(TokenType.BEARER.getValue())
|
||||
.notBefore(Instant.ofEpochSecond(1607633867))
|
||||
.subject("subject1")
|
||||
.audience("audience1")
|
||||
.audience("audience2")
|
||||
.issuer("https://example.com/issuer1")
|
||||
.id("jwtId1")
|
||||
.build();
|
||||
// @formatter:on
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
this.messageConverter.writeInternal(tokenClaims, outputMessage);
|
||||
|
||||
String tokenIntrospectionResponse = outputMessage.getBodyAsString();
|
||||
assertThat(tokenIntrospectionResponse).contains("\"active\":true");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"client_id\":\"clientId1\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"username\":\"username1\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"iat\":1607633867");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"exp\":1607637467");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"scope\":\"scope1 scope2\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"token_type\":\"Bearer\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"nbf\":1607633867");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"sub\":\"subject1\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"aud\":[\"audience1\",\"audience2\"]");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"iss\":\"https://example.com/issuer1\"");
|
||||
assertThat(tokenIntrospectionResponse).contains("\"jti\":\"jwtId1\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void writeInternalWhenWriteFailsThenThrowsException() {
|
||||
String errorMessage = "this is not a valid converter";
|
||||
Converter<OAuth2TokenIntrospection, Map<String, Object>> failingConverter = source -> {
|
||||
throw new RuntimeException(errorMessage);
|
||||
};
|
||||
this.messageConverter.setTokenIntrospectionParametersConverter(failingConverter);
|
||||
|
||||
OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder().build();
|
||||
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
|
||||
assertThatThrownBy(() -> this.messageConverter.writeInternal(tokenClaims, outputMessage))
|
||||
.isInstanceOf(HttpMessageNotWritableException.class)
|
||||
.hasMessageContaining("An error occurred writing the Token Introspection Response")
|
||||
.hasMessageContaining(errorMessage);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* @author Joe Grandja
|
||||
@@ -46,11 +47,22 @@ public class TestOAuth2Authorizations {
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AccessToken accessToken, Map<String, Object> accessTokenClaims) {
|
||||
return authorization(registeredClient, accessToken, accessTokenClaims, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||
return authorization(registeredClient, accessToken, Collections.emptyMap(), authorizationRequestAdditionalParameters);
|
||||
}
|
||||
|
||||
private static OAuth2Authorization.Builder authorization(RegisteredClient registeredClient,
|
||||
OAuth2AccessToken accessToken, Map<String, Object> accessTokenClaims,
|
||||
Map<String, Object> authorizationRequestAdditionalParameters) {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
"code", Instant.now(), Instant.now().plusSeconds(120));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
@@ -66,7 +78,7 @@ public class TestOAuth2Authorizations {
|
||||
.principalName("principal")
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.token(authorizationCode)
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata()))
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(accessTokenClaims)))
|
||||
.refreshToken(refreshToken)
|
||||
.attribute(OAuth2AuthorizationRequest.class.getName(), authorizationRequest)
|
||||
.attribute(Principal.class.getName(),
|
||||
@@ -74,14 +86,21 @@ public class TestOAuth2Authorizations {
|
||||
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizationRequest.getScopes());
|
||||
}
|
||||
|
||||
private static Map<String, Object> tokenMetadata() {
|
||||
private static Map<String, Object> tokenMetadata(Map<String, Object> tokenClaims) {
|
||||
Map<String, Object> tokenMetadata = new HashMap<>();
|
||||
tokenMetadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, false);
|
||||
if (CollectionUtils.isEmpty(tokenClaims)) {
|
||||
tokenClaims = defaultTokenClaims();
|
||||
}
|
||||
tokenMetadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, tokenClaims);
|
||||
return tokenMetadata;
|
||||
}
|
||||
|
||||
private static Map<String, Object> defaultTokenClaims() {
|
||||
Map<String, Object> claims = new HashMap<>();
|
||||
claims.put("claim1", "value1");
|
||||
claims.put("claim2", "value2");
|
||||
claims.put("claim3", "value3");
|
||||
tokenMetadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claims);
|
||||
return tokenMetadata;
|
||||
return claims;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,62 +15,72 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import static java.time.Duration.ofHours;
|
||||
import static java.time.Instant.now;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor.ACTIVE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CLIENT_ID;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.TOKEN_TYPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.USERNAME;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
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.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimNames;
|
||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
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.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenIntrospectionAuthenticationProvider}.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private OAuth2TokenIntrospectionAuthenticationProvider authenticationProvider;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.authenticationProvider = new OAuth2TokenIntrospectionAuthenticationProvider(this.authorizationService);
|
||||
this.authenticationProvider = new OAuth2TokenIntrospectionAuthenticationProvider(
|
||||
this.registeredClientRepository, this.authorizationService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationProvider(null, this.authorizationService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClientRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationProvider(null))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessage("authorizationService cannot be null");
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationProvider(this.registeredClientRepository, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -83,8 +93,10 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret());
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
"token", clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
"token", clientPrincipal, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
|
||||
@@ -95,10 +107,11 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC,
|
||||
null);
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret(), ClientAuthenticationMethod.BASIC, null);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
"token", clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
"token", clientPrincipal, null, null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode")
|
||||
@@ -106,144 +119,157 @@ public class OAuth2TokenIntrospectionAuthenticationProviderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidOAuth2TokenTypeThenThrowOAuth2AuthenticationException() {
|
||||
public void authenticateWhenInvalidTokenThenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
"token", clientPrincipal, "unsupportedOAuth2TokenType");
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isFalse();
|
||||
"token", clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
assertThat(authenticationResult.isAuthenticated()).isFalse();
|
||||
assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1);
|
||||
assertThat(authenticationResult.getTokenClaims().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenTokenNotFoundThenAuthenticatedButTokenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
"token", clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidatedTokenThenAuthenticatedButTokenNotActive() {
|
||||
public void authenticateWhenTokenInvalidatedThenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
authorization = OAuth2AuthenticationProviderUtils.invalidate(authorization, accessToken);
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isFalse();
|
||||
assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1);
|
||||
assertThat(authenticationResult.getTokenClaims().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidAccessTokenThenActiveWithScopes() {
|
||||
public void authenticateWhenTokenExpiredThenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant issuedAt = Instant.now().minus(Duration.ofHours(1));
|
||||
Instant expiresAt = Instant.now().minus(Duration.ofMinutes(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", Instant.now(), Instant.now().plusSeconds(300),
|
||||
new HashSet<>(Arrays.asList("scope1", "Scope2")));
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.accessToken(accessToken).build();
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).token(accessToken).build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isTrue();
|
||||
assertThat(authenticationResult.getClaims()).containsEntry(ACTIVE, true)
|
||||
.containsEntry(IAT, accessToken.getIssuedAt()).containsEntry(EXP, accessToken.getExpiresAt())
|
||||
.containsEntry(TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER).containsEntry(CLIENT_ID, "client-1")
|
||||
.containsEntry(USERNAME, "principal").containsKey(SCOPE)
|
||||
.containsAllEntriesOf(authorization.getAccessToken().getClaims());
|
||||
assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1);
|
||||
assertThat(authenticationResult.getTokenClaims().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenTokenBeforeUseThenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant notBefore = issuedAt.plus(Duration.ofMinutes(5));
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt);
|
||||
Map<String, Object> accessTokenClaims = Collections.singletonMap(JwtClaimNames.NBF, notBefore);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(registeredClient, accessToken, accessTokenClaims)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.getTokenClaims().getClaims()).hasSize(1);
|
||||
assertThat(authenticationResult.getTokenClaims().isActive()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidAccessTokenThenActive() {
|
||||
RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt,
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
JwtClaimsSet jwtClaims = TestJwtClaimsSets.jwtClaimsSet().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(authorizedClient, accessToken, jwtClaims.getClaims())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).thenReturn(authorizedClient);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
TestRegisteredClients.registeredClient2().build());
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
verify(this.registeredClientRepository).findById(eq(authorizedClient.getId()));
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims();
|
||||
assertThat(tokenClaims.isActive()).isTrue();
|
||||
assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId());
|
||||
assertThat(tokenClaims.getIssuedAt()).isEqualTo(accessToken.getIssuedAt());
|
||||
assertThat(tokenClaims.getExpiresAt()).isEqualTo(accessToken.getExpiresAt());
|
||||
assertThat(tokenClaims.getScope()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes());
|
||||
assertThat(tokenClaims.getTokenType()).isEqualTo(accessToken.getTokenType().getValue());
|
||||
assertThat(tokenClaims.getNotBefore()).isEqualTo(jwtClaims.getNotBefore());
|
||||
assertThat(tokenClaims.getSubject()).isEqualTo(jwtClaims.getSubject());
|
||||
assertThat(tokenClaims.getAudience()).containsExactlyInAnyOrderElementsOf(jwtClaims.getAudience());
|
||||
assertThat(tokenClaims.getIssuer()).isEqualTo(jwtClaims.getIssuer());
|
||||
assertThat(tokenClaims.getId()).isEqualTo(jwtClaims.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenValidRefreshTokenThenActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||
RegisteredClient authorizedClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization().build();
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
when(this.authorizationService.findByToken(eq(refreshToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
when(this.registeredClientRepository.findById(eq(authorizedClient.getId()))).thenReturn(authorizedClient);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
TestRegisteredClients.registeredClient2().build());
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
refreshToken.getTokenValue(), clientPrincipal, OAuth2TokenType.REFRESH_TOKEN.getValue());
|
||||
refreshToken.getTokenValue(), clientPrincipal, null, null);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult =
|
||||
(OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
verify(this.authorizationService).findByToken(eq(authentication.getToken()), isNull());
|
||||
verify(this.registeredClientRepository).findById(eq(authorizedClient.getId()));
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isTrue();
|
||||
assertThat(authenticationResult.getClaims()).containsEntry(ACTIVE, true)
|
||||
.containsEntry(IAT, refreshToken.getIssuedAt()).containsEntry(EXP, refreshToken.getExpiresAt())
|
||||
.containsEntry(USERNAME, "principal").containsEntry(CLIENT_ID, "client-1").hasSize(5);
|
||||
OAuth2TokenIntrospection tokenClaims = authenticationResult.getTokenClaims();
|
||||
assertThat(tokenClaims.getClaims()).hasSize(4);
|
||||
assertThat(tokenClaims.isActive()).isTrue();
|
||||
assertThat(tokenClaims.getClientId()).isEqualTo(authorizedClient.getClientId());
|
||||
assertThat(tokenClaims.getIssuedAt()).isEqualTo(refreshToken.getIssuedAt());
|
||||
assertThat(tokenClaims.getExpiresAt()).isEqualTo(refreshToken.getExpiresAt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenExpiredTokenThenAuthenticatedButTokenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant expiresAt = Instant.now().minus(ofHours(1));
|
||||
Instant issuedAt = expiresAt.minus(ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).token(accessToken)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isFalse();
|
||||
assertThat(authenticationResult.getClaims()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidNotBeforeClaimThenAuthenticatedButTokenNotActive() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(ofHours(1));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token", issuedAt, expiresAt);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(
|
||||
accessToken,
|
||||
(metadata) -> metadata.put(
|
||||
OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
|
||||
Collections.singletonMap(JwtClaimNames.NBF, now().plus(ofHours(1)))))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(accessToken.getTokenValue()), isNull()))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken authenticationResult = (OAuth2TokenIntrospectionAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.isTokenActive()).isFalse();
|
||||
assertThat(authenticationResult.getClaims()).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,84 +15,91 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenIntrospectionAuthenticationToken}.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionAuthenticationTokenTests {
|
||||
private String tokenValue = "tokenValue";
|
||||
private String token = "token";
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
TestRegisteredClients.registeredClient().build());
|
||||
private String tokenTypeHint = OAuth2TokenType.ACCESS_TOKEN.getValue();
|
||||
private Map<String, Object> claims = new HashMap<>();
|
||||
private OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true).build();
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenValueNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(
|
||||
() -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.clientPrincipal, this.tokenTypeHint))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessage("token cannot be empty");
|
||||
public void constructorWhenTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.clientPrincipal, null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("token cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(
|
||||
() -> new OAuth2TokenIntrospectionAuthenticationToken(this.tokenValue, null, this.tokenTypeHint))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessage("clientPrincipal cannot be null");
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, null, null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("clientPrincipal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenAndClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.claims))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessage("clientPrincipal cannot be null");
|
||||
public void constructorWhenAuthenticatedAndTokenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(null, this.clientPrincipal, this.tokenClaims))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("token cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenValueProvidedThenCreated() {
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
this.tokenValue, this.clientPrincipal, this.tokenTypeHint);
|
||||
assertThat(authentication.getTokenValue()).isEqualTo(this.tokenValue);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||
assertThat(authentication.getTokenTypeHint()).isEqualTo(this.tokenTypeHint);
|
||||
assertThat(authentication.getClaims()).isNull();
|
||||
assertThat(authentication.isTokenActive()).isFalse();
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
public void constructorWhenAuthenticatedAndClientPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, null, this.tokenClaims))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("clientPrincipal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthenticatedAndTokenClaimsNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionAuthenticationToken(this.token, this.clientPrincipal, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("tokenClaims cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenTokenProvidedThenCreated() {
|
||||
Map<String, Object> additionalParameters = Collections.singletonMap("custom-param", "custom-value");
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
this.clientPrincipal, this.claims);
|
||||
assertThat(authentication.getTokenValue()).isNull();
|
||||
this.token, this.clientPrincipal, OAuth2TokenType.ACCESS_TOKEN.getValue(), additionalParameters);
|
||||
assertThat(authentication.getToken()).isEqualTo(this.token);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||
assertThat(authentication.getClaims()).isEqualTo(this.claims);
|
||||
assertThat(authentication.isTokenActive()).isTrue();
|
||||
assertThat(authentication.getTokenTypeHint()).isNull();
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
assertThat(authentication.getTokenTypeHint()).isEqualTo(OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters);
|
||||
assertThat(authentication.getTokenClaims()).isNotNull();
|
||||
assertThat(authentication.getTokenClaims().isActive()).isFalse();
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenNullTokenProvidedThenCreatedAsTokenNotActive() {
|
||||
public void constructorWhenTokenClaimsProvidedThenCreated() {
|
||||
OAuth2TokenIntrospectionAuthenticationToken authentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
this.clientPrincipal, null);
|
||||
assertThat(authentication.getTokenValue()).isNull();
|
||||
this.token, this.clientPrincipal, this.tokenClaims);
|
||||
assertThat(authentication.getToken()).isEqualTo(this.token);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||
assertThat(authentication.getClaims()).isNull();
|
||||
assertThat(authentication.isTokenActive()).isFalse();
|
||||
assertThat(authentication.getTokenTypeHint()).isNull();
|
||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||
assertThat(authentication.getTokenTypeHint()).isNull();
|
||||
assertThat(authentication.getAdditionalParameters()).isEmpty();
|
||||
assertThat(authentication.getTokenClaims()).isEqualTo(this.tokenClaims);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ public class ProviderSettingsTests {
|
||||
assertThat(providerSettings.tokenEndpoint()).isEqualTo("/oauth2/token");
|
||||
assertThat(providerSettings.jwkSetEndpoint()).isEqualTo("/oauth2/jwks");
|
||||
assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo("/oauth2/revoke");
|
||||
assertThat(providerSettings.tokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -44,6 +45,7 @@ public class ProviderSettingsTests {
|
||||
String tokenEndpoint = "/oauth2/v1/token";
|
||||
String jwkSetEndpoint = "/oauth2/v1/jwks";
|
||||
String tokenRevocationEndpoint = "/oauth2/v1/revoke";
|
||||
String tokenIntrospectionEndpoint = "/oauth2/v1/introspect";
|
||||
String issuer = "https://example.com:9000";
|
||||
|
||||
ProviderSettings providerSettings = new ProviderSettings()
|
||||
@@ -51,13 +53,15 @@ public class ProviderSettingsTests {
|
||||
.authorizationEndpoint(authorizationEndpoint)
|
||||
.tokenEndpoint(tokenEndpoint)
|
||||
.jwkSetEndpoint(jwkSetEndpoint)
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint);
|
||||
.tokenRevocationEndpoint(tokenRevocationEndpoint)
|
||||
.tokenIntrospectionEndpoint(tokenIntrospectionEndpoint);
|
||||
|
||||
assertThat(providerSettings.issuer()).isEqualTo(issuer);
|
||||
assertThat(providerSettings.authorizationEndpoint()).isEqualTo(authorizationEndpoint);
|
||||
assertThat(providerSettings.tokenEndpoint()).isEqualTo(tokenEndpoint);
|
||||
assertThat(providerSettings.jwkSetEndpoint()).isEqualTo(jwkSetEndpoint);
|
||||
assertThat(providerSettings.tokenRevocationEndpoint()).isEqualTo(tokenRevocationEndpoint);
|
||||
assertThat(providerSettings.tokenIntrospectionEndpoint()).isEqualTo(tokenIntrospectionEndpoint);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,6 +107,14 @@ public class ProviderSettingsTests {
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tokenIntrospectionEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
assertThatThrownBy(() -> settings.tokenIntrospectionEndpoint(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jwksEndpointWhenNullThenThrowIllegalArgumentException() {
|
||||
ProviderSettings settings = new ProviderSettings();
|
||||
|
||||
@@ -15,31 +15,20 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimAccessor.ACTIVE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.CLIENT_ID;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.SCOPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames.TOKEN_TYPE;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2.TOKEN;
|
||||
import static org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2.TOKEN_TYPE_HINT;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.EXP;
|
||||
import static org.springframework.security.oauth2.jwt.JwtClaimNames.IAT;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
@@ -52,34 +41,38 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaims;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospection;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames2;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionClaimsHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2TokenIntrospectionHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2TokenIntrospectionEndpointFilter}.
|
||||
*
|
||||
* @author Gerardo Roza
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OAuth2TokenIntrospectionEndpointFilter filter;
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter = new OAuth2ErrorHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2TokenIntrospectionClaims> tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionClaimsHttpMessageConverter();
|
||||
private final Condition<Object> scopesMatchesInAnyOrder = new Condition<>(
|
||||
scopes -> scopes.equals("scope1 Scope2") || scopes.equals("Scope2 scope1"), "scopes match");
|
||||
private final String tokenValue = "token.123";
|
||||
private final HttpMessageConverter<OAuth2TokenIntrospection> tokenIntrospectionHttpResponseConverter =
|
||||
new OAuth2TokenIntrospectionHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2Error> errorHttpResponseConverter =
|
||||
new OAuth2ErrorHttpMessageConverter();
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -95,11 +88,19 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
@Test
|
||||
public void constructorWhenAuthenticationManagerNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionEndpointFilter(null))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessage("authenticationManager cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationManager cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotIntrospectionRequestThenNotProcessed() throws Exception {
|
||||
public void constructorWhenTokenIntrospectionEndpointUriNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2TokenIntrospectionEndpointFilter(this.authenticationManager, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("tokenIntrospectionEndpointUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotTokenIntrospectionRequestThenNotProcessed() throws Exception {
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
@@ -112,7 +113,7 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenIntrospectionRequestGetThenNotProcessed() throws Exception {
|
||||
public void doFilterWhenTokenIntrospectionRequestGetThenNotProcessed() throws Exception {
|
||||
String requestUri = OAuth2TokenIntrospectionEndpointFilter.DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
@@ -125,117 +126,106 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenIntrospectionRequestMissingTokenParamThenInvalidRequestError() throws Exception {
|
||||
public void doFilterWhenTokenIntrospectionRequestMissingTokenThenInvalidRequestError() throws Exception {
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
this.tokenValue, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.removeParameter(TOKEN);
|
||||
"token", OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.removeParameter(OAuth2ParameterNames2.TOKEN);
|
||||
|
||||
doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(
|
||||
TOKEN, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
OAuth2ParameterNames2.TOKEN, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRequestMultipleTokenParamThenInvalidRequestError() throws Exception {
|
||||
public void doFilterWhenTokenIntrospectionRequestMultipleTokenThenInvalidRequestError() throws Exception {
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
this.tokenValue, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.addParameter(TOKEN, "token.456");
|
||||
"token", OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.addParameter(OAuth2ParameterNames2.TOKEN, "other-token");
|
||||
|
||||
doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(
|
||||
TOKEN, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
OAuth2ParameterNames2.TOKEN, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenTokenRequestMultipleTokenTypeHintParamThenInvalidRequestError() throws Exception {
|
||||
public void doFilterWhenTokenIntrospectionRequestMultipleTokenTypeHintThenInvalidRequestError() throws Exception {
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
this.tokenValue, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.addParameter(TOKEN_TYPE_HINT, OAuth2TokenType.REFRESH_TOKEN.getValue());
|
||||
"token", OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.addParameter(OAuth2ParameterNames2.TOKEN_TYPE_HINT, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
|
||||
doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(
|
||||
TOKEN_TYPE_HINT, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
OAuth2ParameterNames2.TOKEN_TYPE_HINT, OAuth2ErrorCodes.INVALID_REQUEST, request);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenIntrospectWithNullClaimsThenNotActiveTokenOkReponse() throws Exception {
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
this.tokenValue, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
Authentication clientPrincipal = setupSecurityContext();
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
clientPrincipal, null);
|
||||
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertNotActiveTokenResponse(response);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doFilterWhenIntrospectWithClaimsThenActiveTokenReponse() throws Exception {
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
this.tokenValue, OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
Authentication clientPrincipal = setupSecurityContext();
|
||||
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(Duration.ofHours(1));
|
||||
String clientId = "clientId";
|
||||
Map<String, Object> tokenIntrospectionClaims = new HashMap<>();
|
||||
tokenIntrospectionClaims.put(ACTIVE, true);
|
||||
tokenIntrospectionClaims.put(CLIENT_ID, clientId);
|
||||
tokenIntrospectionClaims.put(IAT, issuedAt);
|
||||
tokenIntrospectionClaims.put(EXP, expiresAt);
|
||||
tokenIntrospectionClaims.put(TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER);
|
||||
tokenIntrospectionClaims.put(SCOPE, "scope1 Scope2");
|
||||
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
clientPrincipal, tokenIntrospectionClaims);
|
||||
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(tokenIntrospectionAuthentication);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2TokenIntrospectionClaims tokenIntrospectionResponse = readTokenIntrospectionResponse(response);
|
||||
|
||||
Map<String, Object> responseMap = tokenIntrospectionResponse.getClaims();
|
||||
public void doFilterWhenTokenIntrospectionRequestValidThenSuccessResponse() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "token",
|
||||
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
// @formatter:off
|
||||
assertThat(responseMap).contains(
|
||||
entry(ACTIVE, true),
|
||||
entry(CLIENT_ID, clientId),
|
||||
entry(TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue()),
|
||||
entry(EXP, expiresAt.getEpochSecond()),
|
||||
entry(IAT, issuedAt.getEpochSecond()))
|
||||
.hasEntrySatisfying(SCOPE, scopesMatchesInAnyOrder)
|
||||
.hasSize(6);
|
||||
// @formatter: on
|
||||
}
|
||||
OAuth2TokenIntrospection tokenClaims = OAuth2TokenIntrospection.builder(true)
|
||||
.clientId("authorized-client-id")
|
||||
.username("authorizing-username")
|
||||
.issuedAt(accessToken.getIssuedAt())
|
||||
.expiresAt(accessToken.getExpiresAt())
|
||||
.scopes(scopes -> scopes.addAll(accessToken.getScopes()))
|
||||
.tokenType(accessToken.getTokenType().getValue())
|
||||
.notBefore(accessToken.getIssuedAt())
|
||||
.subject("authorizing-subject")
|
||||
.audience("authorized-client-id")
|
||||
.issuer("https://provider.com")
|
||||
.id("jti")
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthenticationResult =
|
||||
new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, tokenClaims);
|
||||
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(tokenIntrospectionAuthenticationResult);
|
||||
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(clientPrincipal);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
MockHttpServletRequest request = createTokenIntrospectionRequest(
|
||||
accessToken.getTokenValue(), OAuth2TokenType.ACCESS_TOKEN.getValue());
|
||||
request.addParameter("custom-param-1", "custom-value-1");
|
||||
request.addParameter("custom-param-2", "custom-value-2");
|
||||
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
ArgumentCaptor<OAuth2TokenIntrospectionAuthenticationToken> tokenIntrospectionAuthentication =
|
||||
ArgumentCaptor.forClass(OAuth2TokenIntrospectionAuthenticationToken.class);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
verify(this.authenticationManager).authenticate(tokenIntrospectionAuthentication.capture());
|
||||
|
||||
private void assertNotActiveTokenResponse(MockHttpServletResponse response) throws Exception {
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2TokenIntrospectionClaims tokenIntrospectionResponse = readTokenIntrospectionResponse(response);
|
||||
assertThat(tokenIntrospectionResponse.getClaims()).containsEntry("active", false).hasSize(1);
|
||||
}
|
||||
assertThat(tokenIntrospectionAuthentication.getValue().getAdditionalParameters())
|
||||
.contains(
|
||||
entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", "custom-value-2"));
|
||||
|
||||
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(),
|
||||
HttpStatus.valueOf(response.getStatus()));
|
||||
return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse);
|
||||
}
|
||||
|
||||
private OAuth2TokenIntrospectionClaims readTokenIntrospectionResponse(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(),
|
||||
HttpStatus.valueOf(response.getStatus()));
|
||||
return this.tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospectionClaims.class, httpResponse);
|
||||
OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(response);
|
||||
assertThat(tokenIntrospectionResponse.isActive()).isEqualTo(tokenClaims.isActive());
|
||||
assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(tokenClaims.getClientId());
|
||||
assertThat(tokenIntrospectionResponse.getUsername()).isEqualTo(tokenClaims.getUsername());
|
||||
assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(
|
||||
tokenClaims.getIssuedAt().minusSeconds(1), tokenClaims.getIssuedAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(
|
||||
tokenClaims.getExpiresAt().minusSeconds(1), tokenClaims.getExpiresAt().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getScope()).containsExactlyInAnyOrderElementsOf(tokenClaims.getScope());
|
||||
assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(tokenClaims.getTokenType());
|
||||
assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween(
|
||||
tokenClaims.getNotBefore().minusSeconds(1), tokenClaims.getNotBefore().plusSeconds(1));
|
||||
assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(tokenClaims.getSubject());
|
||||
assertThat(tokenIntrospectionResponse.getAudience()).containsExactlyInAnyOrderElementsOf(tokenClaims.getAudience());
|
||||
assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(tokenClaims.getIssuer());
|
||||
assertThat(tokenIntrospectionResponse.getId()).isEqualTo(tokenClaims.getId());
|
||||
}
|
||||
|
||||
private void doFilterWhenTokenIntrospectionRequestInvalidParameterThenError(String parameterName, String errorCode,
|
||||
@@ -244,8 +234,6 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
FilterChain filterChain = mock(FilterChain.class);
|
||||
|
||||
setupSecurityContext();
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
@@ -256,23 +244,25 @@ public class OAuth2TokenIntrospectionEndpointFilterTests {
|
||||
assertThat(error.getDescription()).isEqualTo("OAuth 2.0 Token Introspection Parameter: " + parameterName);
|
||||
}
|
||||
|
||||
private static Authentication setupSecurityContext() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
|
||||
securityContext.setAuthentication(clientPrincipal);
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
return clientPrincipal;
|
||||
private OAuth2Error readError(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
|
||||
return this.errorHttpResponseConverter.read(OAuth2Error.class, httpResponse);
|
||||
}
|
||||
|
||||
private OAuth2TokenIntrospection readTokenIntrospectionResponse(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
|
||||
return this.tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse);
|
||||
}
|
||||
|
||||
private static MockHttpServletRequest createTokenIntrospectionRequest(String token, String tokenTypeHint) {
|
||||
String requestUri = OAuth2TokenIntrospectionEndpointFilter.DEFAULT_TOKEN_INTROSPECTION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
request.setServletPath(requestUri);
|
||||
|
||||
request.addParameter(TOKEN, token);
|
||||
request.addParameter(TOKEN_TYPE_HINT, tokenTypeHint);
|
||||
request.addParameter(OAuth2ParameterNames2.TOKEN, token);
|
||||
request.addParameter(OAuth2ParameterNames2.TOKEN_TYPE_HINT, tokenTypeHint);
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user