From af1c96b425e18d6ed4f722c40ad2488dcd234503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Kov=C3=A1=C4=8D?= Date: Wed, 8 Jul 2020 20:17:31 +0200 Subject: [PATCH] Simplify OAuth 2.0 Introspection Attribute Retrieval In order to simplify retrieving of OAuth 2.0 Introspection specific attributes, OAuth2IntrospectionClaimAccessor interface was introduced and also new OAuth2AuthenticatedPrincipal implementing this new interface (OAuth2IntrospectionAuthenticatedPrincipal). Also DefaultOAuth2AuthenticatedPrincipal was replaced by OAuth2IntrospectionAuthenticatedPrincipal in cases where OAuth 2.0 Introspection is performed (NimbusOpaqueTokenIntrospector, NimbusReactiveOpaqueTokenIntrospector). DefaultOAuth2AuthenticatedPrincipal can be still used by applications that introspected the token without OAuth 2.0 Introspection. OAuth2IntrospectionAuthenticatedPrincipal will also be used as a default principal in tests where request is post-processed/mutated by OpaqueTokenRequestPostProcessor/OpaqueTokenMutator. Closes gh-6489 --- .../NimbusOpaqueTokenIntrospector.java | 3 +- ...NimbusReactiveOpaqueTokenIntrospector.java | 3 +- ...h2IntrospectionAuthenticatedPrincipal.java | 108 ++++++++++ .../OAuth2IntrospectionClaimAccessor.java | 144 ++++++++++++++ .../TestOAuth2AuthenticatedPrincipals.java | 3 +- ...paqueTokenAuthenticationProviderTests.java | 8 +- ...kenReactiveAuthenticationManagerTests.java | 12 +- ...rospectionAuthenticatedPrincipalTests.java | 184 ++++++++++++++++++ .../server/SecurityMockServerConfigurers.java | 4 +- .../SecurityMockMvcRequestPostProcessors.java | 4 +- 10 files changed, 454 insertions(+), 19 deletions(-) create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipal.java create mode 100644 oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionClaimAccessor.java create mode 100644 oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipalTests.java diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java index 1f0821cff7..440ba8b9c4 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospector.java @@ -39,7 +39,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.support.BasicAuthenticationInterceptor; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; @@ -232,7 +231,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { } } - return new DefaultOAuth2AuthenticatedPrincipal(claims, authorities); + return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities); } private URL issuer(String uri) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java index 2aa31b792c..05c1e9c4c3 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospector.java @@ -37,7 +37,6 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.util.Assert; import org.springframework.web.reactive.function.BodyInserters; @@ -193,7 +192,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke } } - return new DefaultOAuth2AuthenticatedPrincipal(claims, authorities); + return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities); } private URL issuer(String uri) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipal.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipal.java new file mode 100644 index 0000000000..0f4f925440 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipal.java @@ -0,0 +1,108 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.introspection; + +import static org.springframework.security.core.authority.AuthorityUtils.NO_AUTHORITIES; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; +import org.springframework.util.Assert; + +/** + * A domain object that wraps the attributes of OAuth 2.0 Token Introspection. + * + * @author David Kovac + * @since 5.4 + * @see Introspection Response + */ +public final class OAuth2IntrospectionAuthenticatedPrincipal implements OAuth2AuthenticatedPrincipal, + OAuth2IntrospectionClaimAccessor, Serializable { + private final Map attributes; + private final Collection authorities; + private final String name; + + /** + * Constructs an {@code OAuth2IntrospectionAuthenticatedPrincipal} using the provided parameters. + * + * @param attributes the attributes of the OAuth 2.0 Token Introspection + * @param authorities the authorities of the OAuth 2.0 Token Introspection + */ + public OAuth2IntrospectionAuthenticatedPrincipal(Map attributes, + Collection authorities) { + + this(null, attributes, authorities); + } + + /** + * Constructs an {@code OAuth2IntrospectionAuthenticatedPrincipal} using the provided parameters. + * + * @param name the name attached to the OAuth 2.0 Token Introspection + * @param attributes the attributes of the OAuth 2.0 Token Introspection + * @param authorities the authorities of the OAuth 2.0 Token Introspection + */ + public OAuth2IntrospectionAuthenticatedPrincipal(String name, Map attributes, + Collection authorities) { + + Assert.notEmpty(attributes, "attributes cannot be empty"); + this.attributes = Collections.unmodifiableMap(attributes); + this.authorities = authorities == null ? + NO_AUTHORITIES : Collections.unmodifiableCollection(authorities); + this.name = name == null ? getSubject() : name; + } + + /** + * Gets the attributes of the OAuth 2.0 Token Introspection in map form. + * + * @return a {@link Map} of the attribute's objects keyed by the attribute's names + */ + @Override + public Map getAttributes() { + return this.attributes; + } + + /** + * Get the {@link Collection} of {@link GrantedAuthority}s associated + * with this OAuth 2.0 Token Introspection + * + * @return the OAuth 2.0 Token Introspection authorities + */ + @Override + public Collection getAuthorities() { + return this.authorities; + } + + /** + * {@inheritDoc} + */ + @Override + public String getName() { + return this.name; + } + + /** + * {@inheritDoc} + */ + @Override + public Map getClaims() { + return getAttributes(); + } +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionClaimAccessor.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionClaimAccessor.java new file mode 100644 index 0000000000..18c3c30e78 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionClaimAccessor.java @@ -0,0 +1,144 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.introspection; + +import java.net.URL; +import java.time.Instant; +import java.util.List; + +import org.springframework.security.oauth2.core.ClaimAccessor; + +/** + * A {@link ClaimAccessor} for the "claims" that may be contained + * in the Introspection Response. + * + * @author David Kovac + * @since 5.4 + * @see ClaimAccessor + * @see OAuth2IntrospectionClaimNames + * @see OAuth2IntrospectionAuthenticatedPrincipal + * @see Introspection Response + */ +public interface OAuth2IntrospectionClaimAccessor extends ClaimAccessor { + /** + * Returns the indicator {@code (active)} whether or not the token is currently active + * + * @return the indicator whether or not the token is currently active + */ + default boolean isActive() { + return Boolean.TRUE.equals(this.getClaimAsBoolean(OAuth2IntrospectionClaimNames.ACTIVE)); + } + + /** + * Returns the scopes {@code (scope)} associated with the token + * + * @return the scopes associated with the token + */ + default String getScope() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.SCOPE); + } + + /** + * Returns the client identifier {@code (client_id)} for the token + * + * @return the client identifier for the token + */ + default String getClientId() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.CLIENT_ID); + } + + /** + * Returns a human-readable identifier {@code (username)} for the resource owner that authorized the token + * + * @return a human-readable identifier for the resource owner that authorized the token + */ + default String getUsername() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.USERNAME); + } + + /** + * Returns the type of the token {@code (token_type)}, for example {@code bearer}. + * + * @return the type of the token, for example {@code bearer}. + */ + default String getTokenType() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.TOKEN_TYPE); + } + + /** + * Returns a timestamp {@code (exp)} indicating when the token expires + * + * @return a timestamp indicating when the token expires + */ + default Instant getExpiresAt() { + return this.getClaimAsInstant(OAuth2IntrospectionClaimNames.EXPIRES_AT); + } + + /** + * Returns a timestamp {@code (iat)} indicating when the token was issued + * + * @return a timestamp indicating when the token was issued + */ + default Instant getIssuedAt() { + return this.getClaimAsInstant(OAuth2IntrospectionClaimNames.ISSUED_AT); + } + + /** + * Returns a timestamp {@code (nbf)} indicating when the token is not to be used before + * + * @return a timestamp indicating when the token is not to be used before + */ + default Instant getNotBefore() { + return this.getClaimAsInstant(OAuth2IntrospectionClaimNames.NOT_BEFORE); + } + + /** + * Returns usually a machine-readable identifier {@code (sub)} of the resource owner who authorized the token + * + * @return usually a machine-readable identifier of the resource owner who authorized the token + */ + default String getSubject() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.SUBJECT); + } + + /** + * Returns the intended audience {@code (aud)} for the token + * + * @return the intended audience for the token + */ + default List getAudience() { + return this.getClaimAsStringList(OAuth2IntrospectionClaimNames.AUDIENCE); + } + + /** + * Returns the issuer {@code (iss)} of the token + * + * @return the issuer of the token + */ + default URL getIssuer() { + return this.getClaimAsURL(OAuth2IntrospectionClaimNames.ISSUER); + } + + /** + * Returns the identifier {@code (jti)} for the token + * + * @return the identifier for the token + */ + default String getId() { + return this.getClaimAsString(OAuth2IntrospectionClaimNames.JTI); + } +} diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/core/TestOAuth2AuthenticatedPrincipals.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/core/TestOAuth2AuthenticatedPrincipals.java index c1b593d800..92c9437a34 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/core/TestOAuth2AuthenticatedPrincipals.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/core/TestOAuth2AuthenticatedPrincipals.java @@ -28,6 +28,7 @@ import java.util.function.Consumer; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames; /** @@ -56,7 +57,7 @@ public class TestOAuth2AuthenticatedPrincipals { Collection authorities = Arrays.asList(new SimpleGrantedAuthority("SCOPE_read"), new SimpleGrantedAuthority("SCOPE_write"), new SimpleGrantedAuthority("SCOPE_dolphin")); - return new DefaultOAuth2AuthenticatedPrincipal(attributes, authorities); + return new OAuth2IntrospectionAuthenticatedPrincipal(attributes, authorities); } private static URL url(String url) { diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java index c91fbd8903..61496ff77d 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java @@ -25,9 +25,9 @@ import org.junit.Test; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; @@ -63,9 +63,9 @@ public class OpaqueTokenAuthenticationProviderTests { Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")); - assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); + assertThat(result.getPrincipal()).isInstanceOf(OAuth2IntrospectionAuthenticatedPrincipal.class); - Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); + Map attributes = ((OAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .containsEntry(ACTIVE, true) @@ -85,7 +85,7 @@ public class OpaqueTokenAuthenticationProviderTests { @Test public void authenticateWhenMissingScopeAttributeThenNoAuthorities() { - OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); + OAuth2AuthenticatedPrincipal principal = new OAuth2IntrospectionAuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class); when(introspector.introspect(any())).thenReturn(principal); OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java index 03411ede4e..e7b92f8a45 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java @@ -27,9 +27,9 @@ import reactor.core.publisher.Mono; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; @@ -66,9 +66,9 @@ public class OpaqueTokenReactiveAuthenticationManagerTests { Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")).block(); - assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); + assertThat(result.getPrincipal()).isInstanceOf(OAuth2IntrospectionAuthenticatedPrincipal.class); - Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); + Map attributes = ((OAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .containsEntry(ACTIVE, true) @@ -88,7 +88,7 @@ public class OpaqueTokenReactiveAuthenticationManagerTests { @Test public void authenticateWhenMissingScopeAttributeThenNoAuthorities() { - OAuth2AuthenticatedPrincipal authority = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); + OAuth2AuthenticatedPrincipal authority = new OAuth2IntrospectionAuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class); when(introspector.introspect(any())).thenReturn(Mono.just(authority)); OpaqueTokenReactiveAuthenticationManager provider = @@ -96,9 +96,9 @@ public class OpaqueTokenReactiveAuthenticationManagerTests { Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")).block(); - assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); + assertThat(result.getPrincipal()).isInstanceOf(OAuth2IntrospectionAuthenticatedPrincipal.class); - Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); + Map attributes = ((OAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .doesNotContainKey(SCOPE); diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipalTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipalTests.java new file mode 100644 index 0000000000..83b6f318f4 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/OAuth2IntrospectionAuthenticatedPrincipalTests.java @@ -0,0 +1,184 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.oauth2.server.resource.introspection; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; + +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; + +/** + * Tests for {@link OAuth2IntrospectionAuthenticatedPrincipal} + * + * @author David Kovac + */ +public class OAuth2IntrospectionAuthenticatedPrincipalTests { + private static final String AUTHORITY = "SCOPE_read"; + private static final Collection AUTHORITIES = AuthorityUtils.createAuthorityList(AUTHORITY); + + private static final String SUBJECT = "test-subject"; + + private static final String ACTIVE_CLAIM = "active"; + private static final String CLIENT_ID_CLAIM = "client_id"; + private static final String USERNAME_CLAIM = "username"; + private static final String TOKEN_TYPE_CLAIM = "token_type"; + private static final String EXP_CLAIM = "exp"; + private static final String IAT_CLAIM = "iat"; + private static final String NBF_CLAIM = "nbf"; + private static final String SUB_CLAIM = "sub"; + private static final String AUD_CLAIM = "aud"; + private static final String ISS_CLAIM = "iss"; + private static final String JTI_CLAIM = "jti"; + + private static final boolean ACTIVE_VALUE = true; + private static final String CLIENT_ID_VALUE = "client-id-1"; + private static final String USERNAME_VALUE = "username-1"; + private static final String TOKEN_TYPE_VALUE = "token-type-1"; + private static final long EXP_VALUE = Instant.now().plusSeconds(60).getEpochSecond(); + private static final long IAT_VALUE = Instant.now().getEpochSecond(); + private static final long NBF_VALUE = Instant.now().plusSeconds(5).getEpochSecond(); + private static final String SUB_VALUE = "subject1"; + private static final List AUD_VALUE = Arrays.asList("aud1", "aud2"); + private static final String ISS_VALUE = "https://provider.com"; + private static final String JTI_VALUE = "jwt-id-1"; + + private static final Map CLAIMS; + + static { + CLAIMS = new HashMap<>(); + CLAIMS.put(ACTIVE_CLAIM, ACTIVE_VALUE); + CLAIMS.put(CLIENT_ID_CLAIM, CLIENT_ID_VALUE); + CLAIMS.put(USERNAME_CLAIM, USERNAME_VALUE); + CLAIMS.put(TOKEN_TYPE_CLAIM, TOKEN_TYPE_VALUE); + CLAIMS.put(EXP_CLAIM, EXP_VALUE); + CLAIMS.put(IAT_CLAIM, IAT_VALUE); + CLAIMS.put(NBF_CLAIM, NBF_VALUE); + CLAIMS.put(SUB_CLAIM, SUB_VALUE); + CLAIMS.put(AUD_CLAIM, AUD_VALUE); + CLAIMS.put(ISS_CLAIM, ISS_VALUE); + CLAIMS.put(JTI_CLAIM, JTI_VALUE); + } + + @Test + public void constructorWhenAttributesIsNullOrEmptyThenIllegalArgumentException() { + assertThatCode(() -> new OAuth2IntrospectionAuthenticatedPrincipal(null, AUTHORITIES)) + .isInstanceOf(IllegalArgumentException.class); + + assertThatCode(() -> new OAuth2IntrospectionAuthenticatedPrincipal(Collections.emptyMap(), AUTHORITIES)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void constructorWhenAuthoritiesIsNullOrEmptyThenNoAuthorities() { + Collection authorities = + new OAuth2IntrospectionAuthenticatedPrincipal(CLAIMS, null).getAuthorities(); + assertThat(authorities).isEmpty(); + + authorities = new OAuth2IntrospectionAuthenticatedPrincipal(CLAIMS, + Collections.emptyList()).getAuthorities(); + assertThat(authorities).isEmpty(); + } + + @Test + public void constructorWhenNameIsNullThenFallsbackToSubAttribute() { + OAuth2AuthenticatedPrincipal principal = + new OAuth2IntrospectionAuthenticatedPrincipal(null, CLAIMS, AUTHORITIES); + assertThat(principal.getName()).isEqualTo(CLAIMS.get(SUB_CLAIM)); + } + + @Test + public void constructorWhenAttributesAuthoritiesProvidedThenCreated() { + OAuth2IntrospectionAuthenticatedPrincipal principal = + new OAuth2IntrospectionAuthenticatedPrincipal(CLAIMS, AUTHORITIES); + + assertThat(principal.getName()).isEqualTo(CLAIMS.get(SUB_CLAIM)); + assertThat(principal.getAttributes()).isEqualTo(CLAIMS); + assertThat(principal.getClaims()).isEqualTo(CLAIMS); + assertThat(principal.isActive()).isEqualTo(ACTIVE_VALUE); + assertThat(principal.getClientId()).isEqualTo(CLIENT_ID_VALUE); + assertThat(principal.getUsername()).isEqualTo(USERNAME_VALUE); + assertThat(principal.getTokenType()).isEqualTo(TOKEN_TYPE_VALUE); + assertThat(principal.getExpiresAt().getEpochSecond()).isEqualTo(EXP_VALUE); + assertThat(principal.getIssuedAt().getEpochSecond()).isEqualTo(IAT_VALUE); + assertThat(principal.getNotBefore().getEpochSecond()).isEqualTo(NBF_VALUE); + assertThat(principal.getSubject()).isEqualTo(SUB_VALUE); + assertThat(principal.getAudience()).isEqualTo(AUD_VALUE); + assertThat(principal.getIssuer().toString()).isEqualTo(ISS_VALUE); + assertThat(principal.getId()).isEqualTo(JTI_VALUE); + assertThat(principal.getAuthorities()).hasSize(1); + assertThat(principal.getAuthorities().iterator().next().getAuthority()).isEqualTo(AUTHORITY); + } + + @Test + public void constructorWhenAllParametersProvidedAndValidThenCreated() { + OAuth2IntrospectionAuthenticatedPrincipal principal = + new OAuth2IntrospectionAuthenticatedPrincipal(SUBJECT, CLAIMS, AUTHORITIES); + + assertThat(principal.getName()).isEqualTo(SUBJECT); + assertThat(principal.getAttributes()).isEqualTo(CLAIMS); + assertThat(principal.getClaims()).isEqualTo(CLAIMS); + assertThat(principal.isActive()).isEqualTo(ACTIVE_VALUE); + assertThat(principal.getClientId()).isEqualTo(CLIENT_ID_VALUE); + assertThat(principal.getUsername()).isEqualTo(USERNAME_VALUE); + assertThat(principal.getTokenType()).isEqualTo(TOKEN_TYPE_VALUE); + assertThat(principal.getExpiresAt().getEpochSecond()).isEqualTo(EXP_VALUE); + assertThat(principal.getIssuedAt().getEpochSecond()).isEqualTo(IAT_VALUE); + assertThat(principal.getNotBefore().getEpochSecond()).isEqualTo(NBF_VALUE); + assertThat(principal.getSubject()).isEqualTo(SUB_VALUE); + assertThat(principal.getAudience()).isEqualTo(AUD_VALUE); + assertThat(principal.getIssuer().toString()).isEqualTo(ISS_VALUE); + assertThat(principal.getId()).isEqualTo(JTI_VALUE); + assertThat(principal.getAuthorities()).hasSize(1); + assertThat(principal.getAuthorities().iterator().next().getAuthority()).isEqualTo(AUTHORITY); + } + + @Test + public void getNameWhenInConstructorThenReturns() { + OAuth2AuthenticatedPrincipal principal = + new OAuth2IntrospectionAuthenticatedPrincipal(SUB_VALUE, CLAIMS, AUTHORITIES); + assertThat(principal.getName()).isEqualTo(SUB_VALUE); + } + + @Test + public void getAttributeWhenGivenKeyThenReturnsValue() { + OAuth2AuthenticatedPrincipal principal = + new OAuth2IntrospectionAuthenticatedPrincipal(CLAIMS, AUTHORITIES); + + assertThat((Object) principal.getAttribute(ACTIVE_CLAIM)).isEqualTo(ACTIVE_VALUE); + assertThat((Object) principal.getAttribute(CLIENT_ID_CLAIM)).isEqualTo(CLIENT_ID_VALUE); + assertThat((Object) principal.getAttribute(USERNAME_CLAIM)).isEqualTo(USERNAME_VALUE); + assertThat((Object) principal.getAttribute(TOKEN_TYPE_CLAIM)).isEqualTo(TOKEN_TYPE_VALUE); + assertThat((Object) principal.getAttribute(EXP_CLAIM)).isEqualTo(EXP_VALUE); + assertThat((Object) principal.getAttribute(IAT_CLAIM)).isEqualTo(IAT_VALUE); + assertThat((Object) principal.getAttribute(NBF_CLAIM)).isEqualTo(NBF_VALUE); + assertThat((Object) principal.getAttribute(SUB_CLAIM)).isEqualTo(SUB_VALUE); + assertThat((Object) principal.getAttribute(AUD_CLAIM)).isEqualTo(AUD_VALUE); + assertThat((Object) principal.getAttribute(ISS_CLAIM)).isEqualTo(ISS_VALUE); + assertThat((Object) principal.getAttribute(JTI_CLAIM)).isEqualTo(JTI_VALUE); + } +} diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index a144b42c1d..997f41eb23 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -55,7 +55,6 @@ import org.springframework.security.oauth2.client.web.reactive.result.method.ann import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; @@ -71,6 +70,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames; import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.security.web.server.csrf.CsrfWebFilter; @@ -666,7 +666,7 @@ public class SecurityMockServerConfigurers { } private OAuth2AuthenticatedPrincipal defaultPrincipal() { - return new DefaultOAuth2AuthenticatedPrincipal + return new OAuth2IntrospectionAuthenticatedPrincipal (this.attributes.get(), this.authorities.get()); } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index 92450c408d..866396e754 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -66,7 +66,6 @@ import org.springframework.security.oauth2.client.web.HttpSessionOAuth2Authorize import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.client.web.method.annotation.OAuth2AuthorizedClientArgumentResolver; import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; @@ -82,6 +81,7 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames; import org.springframework.security.test.context.TestSecurityContextHolder; import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers; @@ -1283,7 +1283,7 @@ public final class SecurityMockMvcRequestPostProcessors { } private OAuth2AuthenticatedPrincipal defaultPrincipal() { - return new DefaultOAuth2AuthenticatedPrincipal + return new OAuth2IntrospectionAuthenticatedPrincipal (this.attributes.get(), this.authorities.get()); }