Polish Opaque Token

Use OAuth2AuthenticatedPrincipal
Use BearerTokenAuthentication
Update names to reflect more generic approach.

Fixes gh-7344
Fixes gh-7345
This commit is contained in:
Josh Cummings 2019-09-03 07:01:45 -06:00
parent c019507770
commit 068f4f0147
19 changed files with 222 additions and 520 deletions

View File

@ -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<H extends HttpSecurityBuilder<
}
OpaqueTokenIntrospector introspector = getIntrospector();
OAuth2IntrospectionAuthenticationProvider provider =
new OAuth2IntrospectionAuthenticationProvider(introspector);
OpaqueTokenAuthenticationProvider provider =
new OpaqueTokenAuthenticationProvider(introspector);
http.authenticationProvider(provider);
return http.getSharedObject(AuthenticationManager.class);

View File

@ -87,7 +87,7 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
@ -1867,7 +1867,7 @@ public class ServerHttpSecurity {
}
protected ReactiveAuthenticationManager getAuthenticationManager() {
return new OAuth2IntrospectionReactiveAuthenticationManager(getIntrospector());
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
}
protected ReactiveOpaqueTokenIntrospector getIntrospector() {

View File

@ -78,8 +78,8 @@ import org.springframework.security.core.GrantedAuthority;
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.DefaultOAuth2AuthenticatedPrincipal;
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;
@ -91,7 +91,7 @@ import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.OAuth2IntrospectionAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
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;
@ -159,8 +159,9 @@ public class OAuth2ResourceServerConfigurerTests {
private static final String INTROSPECTION_URI = "https://idp.example.com";
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(), new OAuth2TokenAttributes(JWT_CLAIMS), Collections.emptyList());
private static final BearerTokenAuthentication INTROSPECTION_AUTHENTICATION_TOKEN =
new BearerTokenAuthentication(new DefaultOAuth2AuthenticatedPrincipal(JWT_CLAIMS, Collections.emptyList()),
noScopes(), Collections.emptyList());
@Autowired(required = false)
MockMvc mvc;

View File

@ -1,58 +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.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);
}
}

View File

@ -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
* <a target="_blank" href="https://tools.ietf.org/html/rfc7662">introspection</a>
* process.
*
* @author Josh Cummings
* @since 5.2
*/
@Transient
public class OAuth2IntrospectionAuthenticationToken
extends AbstractOAuth2TokenAuthenticationToken<OAuth2AccessToken> {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private Map<String, Object> 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<? extends GrantedAuthority> 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<? extends GrantedAuthority> 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<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));
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, Object> getTokenAttributes() {
return this.attributes;
}
/**
* {@inheritDoc}
*/
@Override
public String getName() {
return this.name;
}
}

View File

@ -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<String, Object> 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<String, Object> 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<GrantedAuthority> authorities = extractAuthorities(claims);
return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities);
}
private Collection<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {
Collection<String> scopes = (Collection<String>) claims.getOrDefault(SCOPE, Collections.emptyList());
List<GrantedAuthority> 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) {

View File

@ -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<OAuth2IntrospectionAuthenticationToken> 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<BearerTokenAuthentication> 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<GrantedAuthority> 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<GrantedAuthority> extractAuthorities(Map<String, Object> claims) {
Collection<String> scopes = (Collection<String>) claims.getOrDefault(SCOPE, Collections.emptyList());
List<GrantedAuthority> 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",

View File

@ -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<String, RequestEntity<?>> 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<String, Object> 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<String, Object> convertClaimsSet(TokenIntrospectionSuccessResponse response) {
private OAuth2AuthenticatedPrincipal convertClaimsSet(TokenIntrospectionSuccessResponse response) {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Map<String, Object> claims = response.toJSONObject();
if (response.getAudience() != null) {
List<String> 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<String> 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) {

View File

@ -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<Map<String, Object>> introspect(String token) {
public Mono<OAuth2AuthenticatedPrincipal> introspect(String token) {
return Mono.just(token)
.flatMap(this::makeRequest)
.flatMap(this::adaptToNimbusResponse)
@ -150,8 +158,9 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
}
}
private Map<String, Object> convertClaimsSet(TokenIntrospectionSuccessResponse response) {
private OAuth2AuthenticatedPrincipal convertClaimsSet(TokenIntrospectionSuccessResponse response) {
Map<String, Object> claims = response.toJSONObject();
Collection<GrantedAuthority> authorities = new ArrayList<>();
if (response.getAudience() != null) {
List<String> 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<String> 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) {

View File

@ -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<String, Object> introspect(String token);
OAuth2AuthenticatedPrincipal introspect(String token);
}

View File

@ -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<Map<String, Object>> introspect(String token);
Mono<OAuth2AuthenticatedPrincipal> introspect(String token);
}

View File

@ -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<Map<String, Object>> attributesConsumer) {
Map<String, Object> 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<GrantedAuthority> 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);
}
}
}

View File

@ -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<String, Object> 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<GrantedAuthority> 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();
}
}

View File

@ -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<String, Object> 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<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
Map<String, Object> attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.containsEntry(ACTIVE, true)
@ -86,18 +85,16 @@ public class OAuth2IntrospectionAuthenticationProviderTests {
@Test
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
Map<String, Object> 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<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
Map<String, Object> 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);
}
}

View File

@ -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<String, Object> 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<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
Map<String, Object> attributes = ((DefaultOAuth2AuthenticatedPrincipal) result.getPrincipal()).getAttributes();
assertThat(attributes)
.isNotNull()
.containsEntry(ACTIVE, true)
@ -85,18 +88,17 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests {
@Test
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
Map<String, Object> 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<String, Object> attributes = ((OAuth2TokenAttributes) result.getPrincipal()).getAttributes();
Map<String, Object> 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);
}
}

View File

@ -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<String, Object> 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<String, Object> attributes = introspectionClient.introspect("token");
assertThat(attributes)
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token");
assertThat(authority.getAttributes())
.isNotNull()
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
.containsEntry(AUDIENCE, Arrays.asList("aud"))

View File

@ -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<String, Object> 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<String, Object> 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"))

View File

@ -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<String, Object> active() {
Map<String, Object> 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);
}
}
}

View File

@ -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")