Skip to content

Added OAuth2TokenAttributes to wrap attributes #7014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
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;
Expand Down Expand Up @@ -123,7 +124,7 @@ private AbstractAuthenticationToken convert(String token, Map<String, Object> cl
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -65,18 +66,20 @@ public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token,
* @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);
super(token, 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));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -101,7 +102,7 @@ private Mono<OAuth2IntrospectionAuthenticationToken> authenticate(String token)
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2TokenAttributes;
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.OAuth2TokenIntrospectionClient;
Expand Down Expand Up @@ -63,9 +64,9 @@ public void authenticateWhenActiveTokenThenOk() throws Exception {
Authentication result =
provider.authenticate(new BearerTokenAuthenticationToken("token"));

assertThat(result.getPrincipal()).isInstanceOf(Map.class);
assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class);

Map<String, Object> attributes = (Map<String, Object>) result.getPrincipal();
Map<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.containsEntry(ACTIVE, true)
Expand Down Expand Up @@ -94,9 +95,9 @@ public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {

Authentication result =
provider.authenticate(new BearerTokenAuthenticationToken("token"));
assertThat(result.getPrincipal()).isInstanceOf(Map.class);
assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class);

Map<String, Object> attributes = (Map<String, Object>) result.getPrincipal();
Map<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.doesNotContainKey(SCOPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand All @@ -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
Expand All @@ -67,7 +69,8 @@ public void getNameWhenConfiguredInConstructorThenReturnsName() {
@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();
}
Expand All @@ -76,7 +79,7 @@ public void getNameWhenHasNoSubjectThenReturnsNull() {
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
Expand All @@ -90,17 +93,19 @@ public void constructorWhenTokenIsNullThenThrowsException() {
public void constructorWhenAttributesAreNullOrEmptyThenThrowsException() {
assertThatCode(() -> new OAuth2IntrospectionAuthenticationToken(this.token, null, null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("attributes cannot be empty");
.hasMessageContaining("principal cannot be null");

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

@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();
}
Expand All @@ -109,7 +114,7 @@ public void constructorWhenPassingAllAttributesThenTokenIsAuthenticated() {
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
Expand All @@ -126,7 +131,8 @@ public void constructorWhenDefaultParametersThenSetsPrincipalToAttributesCopy()
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);
}
Expand All @@ -136,7 +142,8 @@ public void constructorWhenDefaultParametersThenSetsPrincipalToAttributesCopy()
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Map;

import org.junit.Test;
import org.springframework.security.oauth2.core.OAuth2TokenAttributes;
import reactor.core.publisher.Mono;

import org.springframework.security.core.Authentication;
Expand Down Expand Up @@ -62,9 +63,9 @@ public void authenticateWhenActiveTokenThenOk() throws Exception {
Authentication result =
provider.authenticate(new BearerTokenAuthenticationToken("token")).block();

assertThat(result.getPrincipal()).isInstanceOf(Map.class);
assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class);

Map<String, Object> attributes = (Map<String, Object>) result.getPrincipal();
Map<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.containsEntry(ACTIVE, true)
Expand Down Expand Up @@ -93,9 +94,9 @@ public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {

Authentication result =
provider.authenticate(new BearerTokenAuthenticationToken("token")).block();
assertThat(result.getPrincipal()).isInstanceOf(Map.class);
assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class);

Map<String, Object> attributes = (Map<String, Object>) result.getPrincipal();
Map<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.doesNotContainKey(SCOPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package sample;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenAttributes;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -26,8 +27,8 @@
public class OAuth2ResourceServerController {

@GetMapping("/")
public String index(@AuthenticationPrincipal(expression="['sub']") String subject) {
return String.format("Hello, %s!", subject);
public String index(@AuthenticationPrincipal OAuth2TokenAttributes attributes) {
return String.format("Hello, %s!", (String) attributes.getAttribute("sub"));
}

@GetMapping("/message")
Expand Down