diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 28e59020dc..ab7d35be40 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -37,7 +37,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; -import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; @@ -388,8 +388,8 @@ public final class OAuth2ResourceServerConfigurer 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/OAuth2IntrospectionAuthenticationToken.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java deleted file mode 100644 index 2766a3fa18..0000000000 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationToken.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * 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.server.resource.authentication; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -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; - -/** - * An {@link org.springframework.security.core.Authentication} token that represents a successful authentication as - * obtained through an opaque token - * introspection - * process. - * - * @author Josh Cummings - * @since 5.2 - */ -@Transient -public class OAuth2IntrospectionAuthenticationToken - extends AbstractOAuth2TokenAuthenticationToken { - - private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; - - private Map attributes; - private String name; - - /** - * Constructs a {@link OAuth2IntrospectionAuthenticationToken} with the provided arguments - * - * @param token The verified token - * @param authorities The authorities associated with the given token - */ - public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, - OAuth2TokenAttributes attributes, Collection authorities) { - - this(token, attributes, authorities, null); - } - - /** - * Constructs a {@link OAuth2IntrospectionAuthenticationToken} with the provided arguments - * - * @param token The verified token - * @param authorities The authorities associated with the given token - * @param name The name associated with this token - */ - public OAuth2IntrospectionAuthenticationToken(OAuth2AccessToken token, OAuth2TokenAttributes attributes, - Collection authorities, String name) { - - super(token, attributes, token, authorities); - this.attributes = attributes(attributes); - this.name = name == null ? (String) this.attributes.get(SUBJECT) : name; - setAuthenticated(true); - } - - 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)); - } - - /** - * {@inheritDoc} - */ - @Override - public Map getTokenAttributes() { - return this.attributes; - } - - /** - * {@inheritDoc} - */ - @Override - public String getName() { - return this.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/OpaqueTokenAuthenticationProvider.java similarity index 70% rename from oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProvider.java rename to oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProvider.java index 3260671ac7..204e70099a 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/OpaqueTokenAuthenticationProvider.java @@ -16,11 +16,7 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AbstractAuthenticationToken; @@ -28,20 +24,18 @@ import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; 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.OpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.BearerTokenError; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; +import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; import org.springframework.util.Assert; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUED_AT; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE; /** * An {@link AuthenticationProvider} implementation for opaque @@ -65,20 +59,20 @@ import static org.springframework.security.oauth2.server.resource.introspection. * @since 5.2 * @see AuthenticationProvider */ -public final class OAuth2IntrospectionAuthenticationProvider implements AuthenticationProvider { +public final class OpaqueTokenAuthenticationProvider implements AuthenticationProvider { private static final BearerTokenError DEFAULT_INVALID_TOKEN = invalidToken("An error occurred while attempting to introspect the token: Invalid token"); - private OpaqueTokenIntrospector introspectionClient; + private OpaqueTokenIntrospector introspector; /** - * Creates a {@code OAuth2IntrospectionAuthenticationProvider} with the provided parameters + * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * - * @param introspectionClient The {@link OpaqueTokenIntrospector} to use + * @param introspector The {@link OpaqueTokenIntrospector} to use */ - public OAuth2IntrospectionAuthenticationProvider(OpaqueTokenIntrospector introspectionClient) { - Assert.notNull(introspectionClient, "introspectionClient cannot be null"); - this.introspectionClient = introspectionClient; + public OpaqueTokenAuthenticationProvider(OpaqueTokenIntrospector introspector) { + Assert.notNull(introspector, "introspector cannot be null"); + this.introspector = introspector; } /** @@ -97,15 +91,15 @@ public final class OAuth2IntrospectionAuthenticationProvider implements Authenti } BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication; - Map claims; + OAuth2AuthenticatedPrincipal principal; try { - claims = this.introspectionClient.introspect(bearer.getToken()); + principal = this.introspector.introspect(bearer.getToken()); } catch (OAuth2IntrospectionException failed) { OAuth2Error invalidToken = invalidToken(failed.getMessage()); throw new OAuth2AuthenticationException(invalidToken); } - AbstractAuthenticationToken result = convert(bearer.getToken(), claims); + AbstractAuthenticationToken result = convert(principal, bearer.getToken()); result.setDetails(bearer.getDetails()); return result; } @@ -118,22 +112,12 @@ public final class OAuth2IntrospectionAuthenticationProvider implements Authenti return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication); } - private AbstractAuthenticationToken convert(String token, Map claims) { - Instant iat = (Instant) claims.get(ISSUED_AT); - Instant exp = (Instant) claims.get(EXPIRES_AT); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + private AbstractAuthenticationToken convert(OAuth2AuthenticatedPrincipal principal, String token) { + Instant iat = principal.getAttribute(ISSUED_AT); + Instant exp = principal.getAttribute(EXPIRES_AT); + OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp); - Collection authorities = extractAuthorities(claims); - return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities); - } - - private Collection extractAuthorities(Map claims) { - Collection scopes = (Collection) claims.getOrDefault(SCOPE, Collections.emptyList()); - List authorities = new ArrayList<>(); - for (String scope : scopes) { - authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); - } - return authorities; + return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities()); } private static BearerTokenError invalidToken(String message) { 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/OpaqueTokenReactiveAuthenticationManager.java similarity index 70% rename from oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManager.java rename to oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManager.java index eb7205596b..a2ddcaba4d 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/OpaqueTokenReactiveAuthenticationManager.java @@ -17,32 +17,25 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.time.Instant; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import org.springframework.security.oauth2.core.OAuth2TokenAttributes; import reactor.core.publisher.Mono; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -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.server.resource.introspection.OAuth2IntrospectionException; -import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.BearerTokenError; +import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException; +import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.util.Assert; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUED_AT; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE; /** * An {@link ReactiveAuthenticationManager} implementation for opaque @@ -66,20 +59,20 @@ import static org.springframework.security.oauth2.server.resource.introspection. * @since 5.2 * @see ReactiveAuthenticationManager */ -public class OAuth2IntrospectionReactiveAuthenticationManager implements ReactiveAuthenticationManager { +public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthenticationManager { private static final BearerTokenError DEFAULT_INVALID_TOKEN = invalidToken("An error occurred while attempting to introspect the token: Invalid token"); - private ReactiveOpaqueTokenIntrospector introspectionClient; + private ReactiveOpaqueTokenIntrospector introspector; /** - * Creates a {@code OAuth2IntrospectionReactiveAuthenticationManager} with the provided parameters + * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided parameters * - * @param introspectionClient The {@link ReactiveOpaqueTokenIntrospector} to use + * @param introspector The {@link ReactiveOpaqueTokenIntrospector} to use */ - public OAuth2IntrospectionReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspectionClient) { - Assert.notNull(introspectionClient, "introspectionClient cannot be null"); - this.introspectionClient = introspectionClient; + public OpaqueTokenReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspector) { + Assert.notNull(introspector, "introspector cannot be null"); + this.introspector = introspector; } @Override @@ -92,30 +85,20 @@ public class OAuth2IntrospectionReactiveAuthenticationManager implements Reactiv .cast(Authentication.class); } - private Mono authenticate(String token) { - return this.introspectionClient.introspect(token) - .map(claims -> { - Instant iat = (Instant) claims.get(ISSUED_AT); - Instant exp = (Instant) claims.get(EXPIRES_AT); + private Mono authenticate(String token) { + return this.introspector.introspect(token) + .map(principal -> { + Instant iat = principal.getAttribute(ISSUED_AT); + Instant exp = principal.getAttribute(EXPIRES_AT); // construct token OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp); - Collection authorities = extractAuthorities(claims); - return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities); + return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities()); }) .onErrorMap(OAuth2IntrospectionException.class, this::onError); } - private Collection extractAuthorities(Map claims) { - Collection scopes = (Collection) claims.getOrDefault(SCOPE, Collections.emptyList()); - List authorities = new ArrayList<>(); - for (String scope : scopes) { - authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); - } - return authorities; - } - private static BearerTokenError invalidToken(String message) { try { return new BearerTokenError("invalid_token", 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 c530d8bb73..c4b0569ada 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 @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.introspection; import java.net.URI; import java.net.URL; import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -36,6 +38,10 @@ import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; 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; import org.springframework.util.MultiValueMap; @@ -63,8 +69,10 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { private Converter> requestEntityConverter; private RestOperations restOperations; + private final String authorityPrefix = "SCOPE_"; + /** - * Creates a {@code OAuth2IntrospectionAuthenticationProvider} with the provided parameters + * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * * @param introspectionUri The introspection endpoint uri * @param clientId The client id authorized to introspect @@ -82,7 +90,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { } /** - * Creates a {@code OAuth2IntrospectionAuthenticationProvider} with the provided parameters + * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * * The given {@link RestOperations} should perform its own client authentication against the * introspection endpoint. @@ -122,7 +130,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { * {@inheritDoc} */ @Override - public Map introspect(String token) { + public OAuth2AuthenticatedPrincipal introspect(String token) { RequestEntity requestEntity = this.requestEntityConverter.convert(token); if (requestEntity == null) { throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active"); @@ -189,7 +197,8 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { return (TokenIntrospectionSuccessResponse) introspectionResponse; } - private Map convertClaimsSet(TokenIntrospectionSuccessResponse response) { + private OAuth2AuthenticatedPrincipal convertClaimsSet(TokenIntrospectionSuccessResponse response) { + Collection authorities = new ArrayList<>(); Map claims = response.toJSONObject(); if (response.getAudience() != null) { List audiences = new ArrayList<>(); @@ -216,10 +225,15 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector { claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant()); } if (response.getScope() != null) { - claims.put(SCOPE, Collections.unmodifiableList(response.getScope().toStringList())); + List scopes = Collections.unmodifiableList(response.getScope().toStringList()); + claims.put(SCOPE, scopes); + + for (String scope : scopes) { + authorities.add(new SimpleGrantedAuthority(this.authorityPrefix + scope)); + } } - return claims; + return new DefaultOAuth2AuthenticatedPrincipal(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 7634df6665..9908979d28 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 @@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.introspection; import java.net.URI; import java.net.URL; import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -34,6 +36,10 @@ import reactor.core.publisher.Mono; 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; import org.springframework.web.reactive.function.client.ClientResponse; @@ -59,8 +65,10 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke private URI introspectionUri; private WebClient webClient; + private String authorityPrefix = "SCOPE_"; + /** - * Creates a {@code OAuth2IntrospectionReactiveAuthenticationManager} with the provided parameters + * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided parameters * * @param introspectionUri The introspection endpoint uri * @param clientId The client id authorized to introspect @@ -78,7 +86,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke } /** - * Creates a {@code OAuth2IntrospectionReactiveAuthenticationManager} with the provided parameters + * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided parameters * * @param introspectionUri The introspection endpoint uri * @param webClient The client for performing the introspection request @@ -95,7 +103,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke * {@inheritDoc} */ @Override - public Mono> introspect(String token) { + public Mono introspect(String token) { return Mono.just(token) .flatMap(this::makeRequest) .flatMap(this::adaptToNimbusResponse) @@ -150,8 +158,9 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke } } - private Map convertClaimsSet(TokenIntrospectionSuccessResponse response) { + private OAuth2AuthenticatedPrincipal convertClaimsSet(TokenIntrospectionSuccessResponse response) { Map claims = response.toJSONObject(); + Collection authorities = new ArrayList<>(); if (response.getAudience() != null) { List audiences = new ArrayList<>(); for (Audience audience : response.getAudience()) { @@ -177,10 +186,15 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant()); } if (response.getScope() != null) { - claims.put(SCOPE, Collections.unmodifiableList(response.getScope().toStringList())); + List scopes = Collections.unmodifiableList(response.getScope().toStringList()); + claims.put(SCOPE, scopes); + + for (String scope : scopes) { + authorities.add(new SimpleGrantedAuthority(this.authorityPrefix + scope)); + } } - return claims; + return new DefaultOAuth2AuthenticatedPrincipal(claims, authorities); } private URL issuer(String uri) { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospector.java index 6b17264ace..bb8b1538fc 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/OpaqueTokenIntrospector.java @@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection; import java.util.Map; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; + /** * A contract for introspecting and verifying an OAuth 2.0 token. * @@ -41,5 +43,5 @@ public interface OpaqueTokenIntrospector { * @param token the token to introspect * @return the token's attributes */ - Map introspect(String token); + OAuth2AuthenticatedPrincipal introspect(String token); } diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/ReactiveOpaqueTokenIntrospector.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/ReactiveOpaqueTokenIntrospector.java index 56793d284f..e6c539841a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/ReactiveOpaqueTokenIntrospector.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/introspection/ReactiveOpaqueTokenIntrospector.java @@ -20,6 +20,8 @@ import java.util.Map; import reactor.core.publisher.Mono; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; + /** * A contract for introspecting and verifying an OAuth 2.0 token. * @@ -43,5 +45,5 @@ public interface ReactiveOpaqueTokenIntrospector { * @param token the token to introspect * @return the token's attributes */ - Mono> introspect(String token); + Mono introspect(String token); } 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 new file mode 100644 index 0000000000..c1b593d800 --- /dev/null +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/core/TestOAuth2AuthenticatedPrincipals.java @@ -0,0 +1,69 @@ +/* + * 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.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.time.Instant; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +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.OAuth2IntrospectionClaimNames; + +/** + * Test values of {@link OAuth2AuthenticatedPrincipal}s + * + * @author Josh Cummings + */ +public class TestOAuth2AuthenticatedPrincipals { + public static OAuth2AuthenticatedPrincipal active() { + return active(attributes -> {}); + } + + public static OAuth2AuthenticatedPrincipal active(Consumer> attributesConsumer) { + Map attributes = new HashMap<>(); + attributes.put(OAuth2IntrospectionClaimNames.ACTIVE, true); + attributes.put(OAuth2IntrospectionClaimNames.AUDIENCE, Arrays.asList("https://protected.example.net/resource")); + attributes.put(OAuth2IntrospectionClaimNames.CLIENT_ID, "l238j323ds-23ij4"); + attributes.put(OAuth2IntrospectionClaimNames.EXPIRES_AT, Instant.ofEpochSecond(1419356238)); + attributes.put(OAuth2IntrospectionClaimNames.NOT_BEFORE, Instant.ofEpochSecond(29348723984L)); + attributes.put(OAuth2IntrospectionClaimNames.ISSUER, url("https://server.example.com/")); + attributes.put(OAuth2IntrospectionClaimNames.SCOPE, Arrays.asList("read", "write", "dolphin")); + attributes.put(OAuth2IntrospectionClaimNames.SUBJECT, "Z5O3upPC88QrAjx00dis"); + attributes.put(OAuth2IntrospectionClaimNames.USERNAME, "jdoe"); + attributesConsumer.accept(attributes); + + Collection authorities = + Arrays.asList(new SimpleGrantedAuthority("SCOPE_read"), + new SimpleGrantedAuthority("SCOPE_write"), new SimpleGrantedAuthority("SCOPE_dolphin")); + return new DefaultOAuth2AuthenticatedPrincipal(attributes, authorities); + } + + private static URL url(String url) { + try { + return new URL(url); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} 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 deleted file mode 100644 index 8b4079528f..0000000000 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationTokenTests.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * 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.server.resource.authentication; - -import java.net.URL; -import java.time.Instant; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.minidev.json.JSONObject; -import org.junit.Before; -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; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.CLIENT_ID; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.USERNAME; - -/** - * Tests for {@link OAuth2IntrospectionAuthenticationToken} - * - * @author Josh Cummings - */ -public class OAuth2IntrospectionAuthenticationTokenTests { - private final OAuth2AccessToken token = - new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, - "token", Instant.now(), Instant.now().plusSeconds(3600)); - private final String name = "sub"; - private Map attributesMap = new HashMap<>(); - private final OAuth2TokenAttributes attributes = new OAuth2TokenAttributes(attributesMap); - - @Before - public void setUp() { - this.attributesMap.put(SUBJECT, this.name); - this.attributesMap.put(CLIENT_ID, "client_id"); - this.attributesMap.put(USERNAME, "username"); - } - - @Test - public void getNameWhenConfiguredInConstructorThenReturnsName() { - OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, - AuthorityUtils.createAuthorityList("USER"), this.name); - assertThat(authenticated.getName()).isEqualTo(this.name); - } - - @Test - public void getNameWhenHasNoSubjectThenReturnsNull() { - OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, - new OAuth2TokenAttributes(Collections.singletonMap("claim", "value")), - Collections.emptyList()); - assertThat(authenticated.getName()).isNull(); - } - - @Test - public void getNameWhenTokenHasUsernameThenReturnsUsernameAttribute() { - OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, Collections.emptyList()); - assertThat(authenticated.getName()).isEqualTo(this.attributes.getAttribute(SUBJECT)); - } - - @Test - public void constructorWhenTokenIsNullThenThrowsException() { - assertThatCode(() -> new OAuth2IntrospectionAuthenticationToken(null, this.attributes, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("token cannot be null"); - } - - @Test - public void constructorWhenAttributesAreNullOrEmptyThenThrowsException() { - assertThatCode(() -> new OAuth2IntrospectionAuthenticationToken(this.token, null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("principal cannot be 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, - new OAuth2TokenAttributes(Collections.singletonMap("claim", "value")), - Collections.emptyList(), "harris"); - assertThat(authenticated.isAuthenticated()).isTrue(); - } - - @Test - public void getTokenAttributesWhenHasTokenThenReturnsThem() { - OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, Collections.emptyList()); - assertThat(authenticated.getTokenAttributes()).isEqualTo(this.attributes.getAttributes()); - } - - @Test - public void getAuthoritiesWhenHasAuthoritiesThenReturnsThem() { - List authorities = AuthorityUtils.createAuthorityList("USER"); - OAuth2IntrospectionAuthenticationToken authenticated = - new OAuth2IntrospectionAuthenticationToken(this.token, this.attributes, authorities); - assertThat(authenticated.getAuthorities()).isEqualTo(authorities); - } - - // gh-6843 - @Test - public void constructorWhenDefaultParametersThenSetsPrincipalToAttributesCopy() { - JSONObject attributes = new JSONObject(); - attributes.put("active", true); - OAuth2IntrospectionAuthenticationToken token = - new OAuth2IntrospectionAuthenticationToken(this.token, new OAuth2TokenAttributes(attributes), - Collections.emptyList()); - assertThat(token.getPrincipal()).isNotSameAs(attributes); - assertThat(token.getTokenAttributes()).isNotSameAs(attributes); - } - - // gh-6843 - @Test - public void toStringWhenAttributesContainsURLThenDoesNotFail() throws Exception { - JSONObject attributes = new JSONObject(Collections.singletonMap("iss", new URL("https://idp.example.com"))); - OAuth2IntrospectionAuthenticationToken token = - new OAuth2IntrospectionAuthenticationToken(this.token, new OAuth2TokenAttributes(attributes), - Collections.emptyList()); - assertThatCode(token::toString) - .doesNotThrowAnyException(); - } -} diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProviderTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java similarity index 69% rename from oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProviderTests.java rename to oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java index 8de0c7488f..a2fd4fb11f 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionAuthenticationProviderTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenAuthenticationProviderTests.java @@ -18,23 +18,26 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.net.URL; import java.time.Instant; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import org.junit.Test; 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.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2TokenAttributes; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; 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; -import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals.active; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ACTIVE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; @@ -43,30 +46,26 @@ import static org.springframework.security.oauth2.server.resource.introspection. import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.USERNAME; -import static org.springframework.security.oauth2.server.resource.introspection.TestOAuth2TokenIntrospectionClientResponses.active; /** - * Tests for {@link OAuth2IntrospectionAuthenticationProvider} + * Tests for {@link OpaqueTokenAuthenticationProvider} * * @author Josh Cummings - * @since 5.2 */ -public class OAuth2IntrospectionAuthenticationProviderTests { +public class OpaqueTokenAuthenticationProviderTests { @Test public void authenticateWhenActiveTokenThenOk() throws Exception { - Map claims = active(); - claims.put("extension_field", "twenty-seven"); - OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())).thenReturn(claims); - OAuth2IntrospectionAuthenticationProvider provider = - new OAuth2IntrospectionAuthenticationProvider(introspectionClient); + OAuth2AuthenticatedPrincipal principal = active(attributes -> attributes.put("extension_field", "twenty-seven")); + OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class); + when(introspector.introspect(any())).thenReturn(principal); + OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector); Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")); - assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class); + assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); - Map attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes(); + Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .containsEntry(ACTIVE, true) @@ -86,18 +85,16 @@ public class OAuth2IntrospectionAuthenticationProviderTests { @Test public void authenticateWhenMissingScopeAttributeThenNoAuthorities() { - Map claims = active(); - claims.remove(SCOPE); - OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())).thenReturn(claims); - OAuth2IntrospectionAuthenticationProvider provider = - new OAuth2IntrospectionAuthenticationProvider(introspectionClient); + OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); + OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class); + when(introspector.introspect(any())).thenReturn(principal); + OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector); Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")); - assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class); + assertThat(result.getPrincipal()).isInstanceOf(OAuth2AuthenticatedPrincipal.class); - Map attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes(); + Map attributes = ((OAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .doesNotContainKey(SCOPE); @@ -107,10 +104,9 @@ public class OAuth2IntrospectionAuthenticationProviderTests { @Test public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() { - OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())).thenThrow(new OAuth2IntrospectionException("with \"invalid\" chars")); - OAuth2IntrospectionAuthenticationProvider provider = - new OAuth2IntrospectionAuthenticationProvider(introspectionClient); + OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class); + when(introspector.introspect(any())).thenThrow(new OAuth2IntrospectionException("with \"invalid\" chars")); + OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector); assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token"))) .isInstanceOf(OAuth2AuthenticationException.class) @@ -120,7 +116,7 @@ public class OAuth2IntrospectionAuthenticationProviderTests { @Test public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() { - assertThatCode(() -> new OAuth2IntrospectionAuthenticationProvider(null)) + assertThatCode(() -> new OpaqueTokenAuthenticationProvider(null)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManagerTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java similarity index 69% rename from oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManagerTests.java rename to oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java index edc752dc57..6ab8c90fb8 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OAuth2IntrospectionReactiveAuthenticationManagerTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/OpaqueTokenReactiveAuthenticationManagerTests.java @@ -19,24 +19,27 @@ package org.springframework.security.oauth2.server.resource.authentication; import java.net.URL; import java.time.Instant; import java.util.Arrays; +import java.util.Collections; 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; +import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; 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; -import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals.active; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ACTIVE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; @@ -45,27 +48,27 @@ import static org.springframework.security.oauth2.server.resource.introspection. import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT; import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.USERNAME; -import static org.springframework.security.oauth2.server.resource.introspection.TestOAuth2TokenIntrospectionClientResponses.active; /** - * Tests for {@link OAuth2IntrospectionReactiveAuthenticationManager} + * Tests for {@link OpaqueTokenReactiveAuthenticationManager} + * + * @author Josh Cummings */ -public class OAuth2IntrospectionReactiveAuthenticationManagerTests { +public class OpaqueTokenReactiveAuthenticationManagerTests { @Test public void authenticateWhenActiveTokenThenOk() throws Exception { - Map claims = active(); - claims.put("extension_field", "twenty-seven"); - ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())).thenReturn(Mono.just(claims)); - OAuth2IntrospectionReactiveAuthenticationManager provider = - new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient); + OAuth2AuthenticatedPrincipal authority = active(attributes -> attributes.put("extension_field", "twenty-seven")); + ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class); + when(introspector.introspect(any())).thenReturn(Mono.just(authority)); + OpaqueTokenReactiveAuthenticationManager provider = + new OpaqueTokenReactiveAuthenticationManager(introspector); Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")).block(); - assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class); + assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); - Map attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes(); + Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .containsEntry(ACTIVE, true) @@ -85,18 +88,17 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests { @Test public void authenticateWhenMissingScopeAttributeThenNoAuthorities() { - Map claims = active(); - claims.remove(SCOPE); - ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())).thenReturn(Mono.just(claims)); - OAuth2IntrospectionReactiveAuthenticationManager provider = - new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient); + OAuth2AuthenticatedPrincipal authority = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null); + ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class); + when(introspector.introspect(any())).thenReturn(Mono.just(authority)); + OpaqueTokenReactiveAuthenticationManager provider = + new OpaqueTokenReactiveAuthenticationManager(introspector); Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")).block(); - assertThat(result.getPrincipal()).isInstanceOf(OAuth2TokenAttributes.class); + assertThat(result.getPrincipal()).isInstanceOf(DefaultOAuth2AuthenticatedPrincipal.class); - Map attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes(); + Map attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes(); assertThat(attributes) .isNotNull() .doesNotContainKey(SCOPE); @@ -106,11 +108,11 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests { @Test public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() { - ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class); - when(introspectionClient.introspect(any())) + ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class); + when(introspector.introspect(any())) .thenReturn(Mono.error(new OAuth2IntrospectionException("with \"invalid\" chars"))); - OAuth2IntrospectionReactiveAuthenticationManager provider = - new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient); + OpaqueTokenReactiveAuthenticationManager provider = + new OpaqueTokenReactiveAuthenticationManager(introspector); assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")).block()) .isInstanceOf(OAuth2AuthenticationException.class) @@ -120,7 +122,7 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests { @Test public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() { - assertThatCode(() -> new OAuth2IntrospectionReactiveAuthenticationManager(null)) + assertThatCode(() -> new OpaqueTokenReactiveAuthenticationManager(null)) .isInstanceOf(IllegalArgumentException.class); } } diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java index f4464a75ef..7ce0871922 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusOpaqueTokenIntrospectorTests.java @@ -38,6 +38,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.web.client.RestOperations; import static org.assertj.core.api.Assertions.assertThat; @@ -113,8 +114,8 @@ public class NimbusOpaqueTokenIntrospectorTests { OpaqueTokenIntrospector introspectionClient = new NimbusOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET); - Map attributes = introspectionClient.introspect("token"); - assertThat(attributes) + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token"); + assertThat(authority.getAttributes()) .isNotNull() .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) .containsEntry(AUDIENCE, Arrays.asList("https://protected.example.net/resource")) @@ -168,8 +169,8 @@ public class NimbusOpaqueTokenIntrospectorTests { when(restOperations.exchange(any(RequestEntity.class), eq(String.class))) .thenReturn(response(new JSONObject(introspectedValues).toJSONString())); - Map attributes = introspectionClient.introspect("token"); - assertThat(attributes) + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token"); + assertThat(authority.getAttributes()) .isNotNull() .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) .containsEntry(AUDIENCE, Arrays.asList("aud")) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java index 54ccbe9c55..0b58f45e6b 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/NimbusReactiveOpaqueTokenIntrospectorTests.java @@ -36,6 +36,7 @@ import reactor.core.publisher.Mono; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.WebClient; @@ -103,8 +104,8 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { NimbusReactiveOpaqueTokenIntrospector introspectionClient = new NimbusReactiveOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET); - Map attributes = introspectionClient.introspect("token").block(); - assertThat(attributes) + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block(); + assertThat(authority.getAttributes()) .isNotNull() .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) .containsEntry(AUDIENCE, Arrays.asList("https://protected.example.net/resource")) @@ -155,8 +156,8 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests { NimbusReactiveOpaqueTokenIntrospector introspectionClient = new NimbusReactiveOpaqueTokenIntrospector(INTROSPECTION_URL, webClient); - Map attributes = introspectionClient.introspect("token").block(); - assertThat(attributes) + OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block(); + assertThat(authority.getAttributes()) .isNotNull() .containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true) .containsEntry(AUDIENCE, Arrays.asList("aud")) diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/TestOAuth2TokenIntrospectionClientResponses.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/TestOAuth2TokenIntrospectionClientResponses.java deleted file mode 100644 index 27345522a2..0000000000 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/introspection/TestOAuth2TokenIntrospectionClientResponses.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.server.resource.introspection; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URL; -import java.time.Instant; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ACTIVE; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.CLIENT_ID; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUER; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.NOT_BEFORE; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SCOPE; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT; -import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.USERNAME; - -public class TestOAuth2TokenIntrospectionClientResponses { - public static Map active() { - Map attributes = new HashMap<>(); - attributes.put(ACTIVE, true); - attributes.put(AUDIENCE, Arrays.asList("https://protected.example.net/resource")); - attributes.put(CLIENT_ID, "l238j323ds-23ij4"); - attributes.put(EXPIRES_AT, Instant.ofEpochSecond(1419356238)); - attributes.put(NOT_BEFORE, Instant.ofEpochSecond(29348723984L)); - attributes.put(ISSUER, url("https://server.example.com/")); - attributes.put(SCOPE, Arrays.asList("read", "write", "dolphin")); - attributes.put(SUBJECT, "Z5O3upPC88QrAjx00dis"); - attributes.put(USERNAME, "jdoe"); - return attributes; - } - - private static URL url(String url) { - try { - return new URL(url); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } -} diff --git a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java index 93f6019870..bf3fb9f57d 100644 --- a/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java +++ b/samples/boot/oauth2resourceserver-opaque/src/main/java/sample/OAuth2ResourceServerController.java @@ -16,7 +16,7 @@ package sample; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.oauth2.core.OAuth2TokenAttributes; +import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -27,8 +27,8 @@ import org.springframework.web.bind.annotation.RestController; public class OAuth2ResourceServerController { @GetMapping("/") - public String index(@AuthenticationPrincipal OAuth2TokenAttributes attributes) { - return String.format("Hello, %s!", (String) attributes.getAttribute("sub")); + public String index(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal) { + return String.format("Hello, %s!", (String) principal.getAttribute("sub")); } @GetMapping("/message")