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()); }