Jelajahi Sumber

Added OAuth2TokenAttributes to wrap attributes

To simplify access to OAuth 2.0 token attributes

Fixes gh-6498
Clement Ng 6 tahun lalu
induk
melakukan
491da9db03

+ 2 - 1
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;

+ 58 - 0
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<String, Object> attributes;
+
+	/**
+	 * Constructs an {@code OAuth2TokenAttributes} using the provided parameters.
+	 *
+	 * @param attributes the attributes of the OAuth 2.0 token
+	 */
+	public OAuth2TokenAttributes(Map<String, Object> 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<String, Object> 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> A getAttribute(String name) {
+		return (A) this.attributes.get(name);
+	}
+}

+ 2 - 1
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<GrantedAuthority> authorities = extractAuthorities(claims);
-		return new OAuth2IntrospectionAuthenticationToken(accessToken, claims, authorities);
+		return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities);
 	}
 
 	private Collection<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {

+ 10 - 7
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<String, Object> attributes, Collection<? extends GrantedAuthority> authorities) {
+			OAuth2TokenAttributes attributes, Collection<? extends GrantedAuthority> 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<String, Object> attributes, Collection<? extends GrantedAuthority> authorities, String name) {
+	public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, OAuth2TokenAttributes attributes,
+		Collection<? extends GrantedAuthority> 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<String, Object> attributes(Map<String, Object> attributes) {
-		Assert.notEmpty(attributes, "attributes cannot be empty");
-		return Collections.unmodifiableMap(new LinkedHashMap<>(attributes));
+	private static Map<String, Object> attributes(OAuth2TokenAttributes attributes) {
+		Assert.notNull(attributes, "attributes cannot be empty");
+		Map<String, Object> attr = attributes.getAttributes();
+		Assert.notEmpty(attr, "attributes cannot be empty");
+		return Collections.unmodifiableMap(new LinkedHashMap<>(attr));
 	}
 
 	/**

+ 2 - 1
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<GrantedAuthority> authorities = extractAuthorities(claims);
-					return new OAuth2IntrospectionAuthenticationToken(accessToken, claims, authorities);
+					return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities);
 				})
 				.onErrorMap(OAuth2IntrospectionException.class, this::onError);
 	}

+ 18 - 11
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<String, Object> attributes = new HashMap<>();
 	private final String name = "sub";
+	private Map<String, Object> 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();
 	}