From 491da9db03c8919d802eeb098eaa44ab0cdc35e1 Mon Sep 17 00:00:00 2001 From: Clement Ng Date: Tue, 18 Jun 2019 00:49:57 -0700 Subject: [PATCH] Added OAuth2TokenAttributes to wrap attributes To simplify access to OAuth 2.0 token attributes Fixes gh-6498 --- .../OAuth2ResourceServerConfigurerTests.java | 3 +- .../oauth2/core/OAuth2TokenAttributes.java | 58 +++++++++++++++++++ ...h2IntrospectionAuthenticationProvider.java | 3 +- ...Auth2IntrospectionAuthenticationToken.java | 17 +++--- ...spectionReactiveAuthenticationManager.java | 3 +- ...IntrospectionAuthenticationTokenTests.java | 29 ++++++---- 6 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenAttributes.java diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 0a1dd12e4a..995e7d98a8 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -79,6 +79,7 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; @@ -159,7 +160,7 @@ public class OAuth2ResourceServerConfigurerTests { private static final String CLIENT_ID = "client-id"; private static final String CLIENT_SECRET = "client-secret"; private static final OAuth2IntrospectionAuthenticationToken INTROSPECTION_AUTHENTICATION_TOKEN = - new OAuth2IntrospectionAuthenticationToken(noScopes(), JWT_CLAIMS, Collections.emptyList()); + new OAuth2IntrospectionAuthenticationToken(noScopes(), new OAuth2TokenAttributes(JWT_CLAIMS), Collections.emptyList()); @Autowired(required = false) MockMvc mvc; diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenAttributes.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenAttributes.java new file mode 100644 index 0000000000..c2c1eada53 --- /dev/null +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/OAuth2TokenAttributes.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2019 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; + +import java.util.Collections; +import java.util.Map; + +/** + * A domain object that wraps the attributes of an OAuth 2.0 token. + * + * @author Clement Ng + * @since 5.2 + */ +public final class OAuth2TokenAttributes { + private final Map attributes; + + /** + * Constructs an {@code OAuth2TokenAttributes} using the provided parameters. + * + * @param attributes the attributes of the OAuth 2.0 token + */ + public OAuth2TokenAttributes(Map attributes) { + this.attributes = Collections.unmodifiableMap(attributes); + } + + /** + * Gets the attributes of the OAuth 2.0 token in map form. + * + * @return a {@link Map} of the attribute's objects keyed by the attribute's names + */ + public Map getAttributes() { + return attributes; + } + + /** + * Gets the attribute of the OAuth 2.0 token corresponding to the name. + * + * @param name the name to lookup in the attributes + * @return the object corresponding to the name in the attributes + */ + public A getAttribute(String name) { + return (A) this.attributes.get(name); + } +} diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProvider.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProvider.java index 37aff3720e..36b7f8ecba 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProvider.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProvider.java @@ -32,6 +32,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; +import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; import org.springframework.security.oauth2.server.resource.introspection.OAuth2TokenIntrospectionClient; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; @@ -123,7 +124,7 @@ public final class OAuth2IntrospectionAuthenticationProvider implements Authenti OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp); Collection authorities = extractAuthorities(claims); - return new OAuth2IntrospectionAuthenticationToken(accessToken, claims, authorities); + return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities); } private Collection extractAuthorities(Map claims) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java index 72f2f96fa6..a67d8726bd 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java @@ -24,6 +24,7 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.security.core.Transient; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import org.springframework.util.Assert; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT; @@ -53,7 +54,7 @@ public class OAuth2IntrospectionAuthenticationToken * @param authorities The authorities associated with the given token */ public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, - Map attributes, Collection authorities) { + OAuth2TokenAttributes attributes, Collection authorities) { this(token, attributes, authorities, null); } @@ -65,18 +66,20 @@ public class OAuth2IntrospectionAuthenticationToken * @param authorities The authorities associated with the given token * @param name The name associated with this token */ - public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, - Map attributes, Collection authorities, String name) { + public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, OAuth2TokenAttributes attributes, + Collection authorities, String name) { super(token, attributes(attributes), token, authorities); this.attributes = attributes(attributes); - this.name = name == null ? (String) attributes.get(SUBJECT) : name; + this.name = name == null ? (String) this.attributes.get(SUBJECT) : name; setAuthenticated(true); } - private static Map attributes(Map attributes) { - Assert.notEmpty(attributes, "attributes cannot be empty"); - return Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); + private static Map attributes(OAuth2TokenAttributes attributes) { + Assert.notNull(attributes, "attributes cannot be empty"); + Map attr = attributes.getAttributes(); + Assert.notEmpty(attr, "attributes cannot be empty"); + return Collections.unmodifiableMap(new LinkedHashMap<>(attr)); } /** diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManager.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManager.java index a48d8407aa..fea7607303 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManager.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManager.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import reactor.core.publisher.Mono; import org.springframework.http.HttpStatus; @@ -101,7 +102,7 @@ public class OAuth2IntrospectionReactiveAuthenticationManager implements Reactiv OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp); Collection authorities = extractAuthorities(claims); - return new OAuth2IntrospectionAuthenticationToken(accessToken, claims, authorities); + return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities); }) .onErrorMap(OAuth2IntrospectionException.class, this::onError); } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationTokenTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationTokenTests.java index b20172fa95..65da34279e 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationTokenTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationTokenTests.java @@ -30,6 +30,7 @@ import org.junit.Test; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -46,14 +47,15 @@ public class OAuth2IntrospectionAuthenticationTokenTests { private final OAuth2AccessToken token = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", Instant.now(), Instant.now().plusSeconds(3600)); - private final Map attributes = new HashMap<>(); private final String name = "sub"; + private Map attributesMap = new HashMap<>(); + private final OAuth2TokenAttributes attributes = new OAuth2TokenAttributes(attributesMap); @Before public void setUp() { - this.attributes.put(SUBJECT, this.name); - this.attributes.put(CLIENT_ID, "client_id"); - this.attributes.put(USERNAME, "username"); + this.attributesMap.put(SUBJECT, this.name); + this.attributesMap.put(CLIENT_ID, "client_id"); + this.attributesMap.put(USERNAME, "username"); } @Test @@ -67,7 +69,8 @@ public class OAuth2IntrospectionAuthenticationTokenTests { @Test public void getNameWhenHasNoSubjectThenReturnsNull() { OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, Collections.singletonMap("claim", "value"), + new OAuth2IntrospectionAuthenticationToken(this.token, + new OAuth2TokenAttributes(Collections.singletonMap("claim", "value")), Collections.emptyList()); assertThat(authenticated.getName()).isNull(); } @@ -76,7 +79,7 @@ public class OAuth2IntrospectionAuthenticationTokenTests { public void getNameWhenTokenHasUsernameThenReturnsUsernameAttribute() { OAuth2IntrospectionAuthenticationToken authenticated = new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, Collections.emptyList()); - assertThat(authenticated.getName()).isEqualTo(this.attributes.get(SUBJECT)); + assertThat(authenticated.getName()).isEqualTo(this.attributes.getAttribute(SUBJECT)); } @Test @@ -92,7 +95,8 @@ public class OAuth2IntrospectionAuthenticationTokenTests { .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("attributes cannot be empty"); - assertThatCode(() -> new OAuth2IntrospectionAuthenticationToken(this.token, Collections.emptyMap(), null)) + assertThatCode(() -> new OAuth2IntrospectionAuthenticationToken(this.token, + new OAuth2TokenAttributes(Collections.emptyMap()), null)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("attributes cannot be empty"); } @@ -100,7 +104,8 @@ public class OAuth2IntrospectionAuthenticationTokenTests { @Test public void constructorWhenPassingAllAttributesThenTokenIsAuthenticated() { OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, Collections.singletonMap("claim", "value"), + new OAuth2IntrospectionAuthenticationToken(this.token, + new OAuth2TokenAttributes(Collections.singletonMap("claim", "value")), Collections.emptyList(), "harris"); assertThat(authenticated.isAuthenticated()).isTrue(); } @@ -109,7 +114,7 @@ public class OAuth2IntrospectionAuthenticationTokenTests { public void getTokenAttributesWhenHasTokenThenReturnsThem() { OAuth2IntrospectionAuthenticationToken authenticated = new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, Collections.emptyList()); - assertThat(authenticated.getTokenAttributes()).isEqualTo(this.attributes); + assertThat(authenticated.getTokenAttributes()).isEqualTo(this.attributes.getAttributes()); } @Test @@ -126,7 +131,8 @@ public class OAuth2IntrospectionAuthenticationTokenTests { JSONObject attributes = new JSONObject(); attributes.put("active", true); OAuth2IntrospectionAuthenticationToken token = - new OAuth2IntrospectionAuthenticationToken(this.token, attributes, Collections.emptyList()); + new OAuth2IntrospectionAuthenticationToken(this.token, new OAuth2TokenAttributes(attributes), + Collections.emptyList()); assertThat(token.getPrincipal()).isNotSameAs(attributes); assertThat(token.getTokenAttributes()).isNotSameAs(attributes); } @@ -136,7 +142,8 @@ public class OAuth2IntrospectionAuthenticationTokenTests { public void toStringWhenAttributesContainsURLThenDoesNotFail() throws Exception { JSONObject attributes = new JSONObject(Collections.singletonMap("iss", new URL("https://idp.example.com"))); OAuth2IntrospectionAuthenticationToken token = - new OAuth2IntrospectionAuthenticationToken(this.token, attributes, Collections.emptyList()); + new OAuth2IntrospectionAuthenticationToken(this.token, new OAuth2TokenAttributes(attributes), + Collections.emptyList()); assertThatCode(token::toString) .doesNotThrowAnyException(); }