mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-24 11:13:30 +00:00
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:
parent
c019507770
commit
068f4f0147
@ -37,7 +37,7 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
|
|||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
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.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.NimbusOpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||||
@ -388,8 +388,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
}
|
}
|
||||||
|
|
||||||
OpaqueTokenIntrospector introspector = getIntrospector();
|
OpaqueTokenIntrospector introspector = getIntrospector();
|
||||||
OAuth2IntrospectionAuthenticationProvider provider =
|
OpaqueTokenAuthenticationProvider provider =
|
||||||
new OAuth2IntrospectionAuthenticationProvider(introspector);
|
new OpaqueTokenAuthenticationProvider(introspector);
|
||||||
http.authenticationProvider(provider);
|
http.authenticationProvider(provider);
|
||||||
|
|
||||||
return http.getSharedObject(AuthenticationManager.class);
|
return http.getSharedObject(AuthenticationManager.class);
|
||||||
|
@ -87,7 +87,7 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
|||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
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.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.authentication.ReactiveJwtAuthenticationConverterAdapter;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||||
@ -1867,7 +1867,7 @@ public class ServerHttpSecurity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
||||||
return new OAuth2IntrospectionReactiveAuthenticationManager(getIntrospector());
|
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
|
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
|
||||||
|
@ -78,8 +78,8 @@ import org.springframework.security.core.GrantedAuthority;
|
|||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
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.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenAttributes;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
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.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
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.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.NimbusOpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
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 INTROSPECTION_URI = "https://idp.example.com";
|
||||||
private static final String CLIENT_ID = "client-id";
|
private static final String CLIENT_ID = "client-id";
|
||||||
private static final String CLIENT_SECRET = "client-secret";
|
private static final String CLIENT_SECRET = "client-secret";
|
||||||
private static final OAuth2IntrospectionAuthenticationToken INTROSPECTION_AUTHENTICATION_TOKEN =
|
private static final BearerTokenAuthentication INTROSPECTION_AUTHENTICATION_TOKEN =
|
||||||
new OAuth2IntrospectionAuthenticationToken(noScopes(), new OAuth2TokenAttributes(JWT_CLAIMS), Collections.emptyList());
|
new BearerTokenAuthentication(new DefaultOAuth2AuthenticatedPrincipal(JWT_CLAIMS, Collections.emptyList()),
|
||||||
|
noScopes(), Collections.emptyList());
|
||||||
|
|
||||||
@Autowired(required = false)
|
@Autowired(required = false)
|
||||||
MockMvc mvc;
|
MockMvc mvc;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,11 +16,7 @@
|
|||||||
package org.springframework.security.oauth2.server.resource.authentication;
|
package org.springframework.security.oauth2.server.resource.authentication;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
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.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
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.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
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.BearerTokenAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
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 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.EXPIRES_AT;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUED_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
|
* An {@link AuthenticationProvider} implementation for opaque
|
||||||
@ -65,20 +59,20 @@ import static org.springframework.security.oauth2.server.resource.introspection.
|
|||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see AuthenticationProvider
|
* @see AuthenticationProvider
|
||||||
*/
|
*/
|
||||||
public final class OAuth2IntrospectionAuthenticationProvider implements AuthenticationProvider {
|
public final class OpaqueTokenAuthenticationProvider implements AuthenticationProvider {
|
||||||
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
|
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
|
||||||
invalidToken("An error occurred while attempting to introspect the token: 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) {
|
public OpaqueTokenAuthenticationProvider(OpaqueTokenIntrospector introspector) {
|
||||||
Assert.notNull(introspectionClient, "introspectionClient cannot be null");
|
Assert.notNull(introspector, "introspector cannot be null");
|
||||||
this.introspectionClient = introspectionClient;
|
this.introspector = introspector;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,15 +91,15 @@ public final class OAuth2IntrospectionAuthenticationProvider implements Authenti
|
|||||||
}
|
}
|
||||||
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
|
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
|
||||||
|
|
||||||
Map<String, Object> claims;
|
OAuth2AuthenticatedPrincipal principal;
|
||||||
try {
|
try {
|
||||||
claims = this.introspectionClient.introspect(bearer.getToken());
|
principal = this.introspector.introspect(bearer.getToken());
|
||||||
} catch (OAuth2IntrospectionException failed) {
|
} catch (OAuth2IntrospectionException failed) {
|
||||||
OAuth2Error invalidToken = invalidToken(failed.getMessage());
|
OAuth2Error invalidToken = invalidToken(failed.getMessage());
|
||||||
throw new OAuth2AuthenticationException(invalidToken);
|
throw new OAuth2AuthenticationException(invalidToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractAuthenticationToken result = convert(bearer.getToken(), claims);
|
AbstractAuthenticationToken result = convert(principal, bearer.getToken());
|
||||||
result.setDetails(bearer.getDetails());
|
result.setDetails(bearer.getDetails());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -118,22 +112,12 @@ public final class OAuth2IntrospectionAuthenticationProvider implements Authenti
|
|||||||
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AbstractAuthenticationToken convert(String token, Map<String, Object> claims) {
|
private AbstractAuthenticationToken convert(OAuth2AuthenticatedPrincipal principal, String token) {
|
||||||
Instant iat = (Instant) claims.get(ISSUED_AT);
|
Instant iat = principal.getAttribute(ISSUED_AT);
|
||||||
Instant exp = (Instant) claims.get(EXPIRES_AT);
|
Instant exp = principal.getAttribute(EXPIRES_AT);
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
token, iat, exp);
|
token, iat, exp);
|
||||||
Collection<GrantedAuthority> authorities = extractAuthorities(claims);
|
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BearerTokenError invalidToken(String message) {
|
private static BearerTokenError invalidToken(String message) {
|
@ -17,32 +17,25 @@
|
|||||||
package org.springframework.security.oauth2.server.resource.authentication;
|
package org.springframework.security.oauth2.server.resource.authentication;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
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 reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
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.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
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.BearerTokenAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.resource.BearerTokenError;
|
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 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.EXPIRES_AT;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.ISSUED_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
|
* An {@link ReactiveAuthenticationManager} implementation for opaque
|
||||||
@ -66,20 +59,20 @@ import static org.springframework.security.oauth2.server.resource.introspection.
|
|||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see ReactiveAuthenticationManager
|
* @see ReactiveAuthenticationManager
|
||||||
*/
|
*/
|
||||||
public class OAuth2IntrospectionReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthenticationManager {
|
||||||
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
|
private static final BearerTokenError DEFAULT_INVALID_TOKEN =
|
||||||
invalidToken("An error occurred while attempting to introspect the token: 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) {
|
public OpaqueTokenReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspector) {
|
||||||
Assert.notNull(introspectionClient, "introspectionClient cannot be null");
|
Assert.notNull(introspector, "introspector cannot be null");
|
||||||
this.introspectionClient = introspectionClient;
|
this.introspector = introspector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -92,30 +85,20 @@ public class OAuth2IntrospectionReactiveAuthenticationManager implements Reactiv
|
|||||||
.cast(Authentication.class);
|
.cast(Authentication.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<OAuth2IntrospectionAuthenticationToken> authenticate(String token) {
|
private Mono<BearerTokenAuthentication> authenticate(String token) {
|
||||||
return this.introspectionClient.introspect(token)
|
return this.introspector.introspect(token)
|
||||||
.map(claims -> {
|
.map(principal -> {
|
||||||
Instant iat = (Instant) claims.get(ISSUED_AT);
|
Instant iat = principal.getAttribute(ISSUED_AT);
|
||||||
Instant exp = (Instant) claims.get(EXPIRES_AT);
|
Instant exp = principal.getAttribute(EXPIRES_AT);
|
||||||
|
|
||||||
// construct token
|
// construct token
|
||||||
OAuth2AccessToken accessToken =
|
OAuth2AccessToken accessToken =
|
||||||
new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
||||||
Collection<GrantedAuthority> authorities = extractAuthorities(claims);
|
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
||||||
return new OAuth2IntrospectionAuthenticationToken(accessToken, new OAuth2TokenAttributes(claims), authorities);
|
|
||||||
})
|
})
|
||||||
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
|
.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) {
|
private static BearerTokenError invalidToken(String message) {
|
||||||
try {
|
try {
|
||||||
return new BearerTokenError("invalid_token",
|
return new BearerTokenError("invalid_token",
|
@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -36,6 +38,10 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.client.support.BasicAuthenticationInterceptor;
|
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.Assert;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
@ -63,8 +69,10 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
private Converter<String, RequestEntity<?>> requestEntityConverter;
|
private Converter<String, RequestEntity<?>> requestEntityConverter;
|
||||||
private RestOperations restOperations;
|
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 introspectionUri The introspection endpoint uri
|
||||||
* @param clientId The client id authorized to introspect
|
* @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
|
* The given {@link RestOperations} should perform its own client authentication against the
|
||||||
* introspection endpoint.
|
* introspection endpoint.
|
||||||
@ -122,7 +130,7 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> introspect(String token) {
|
public OAuth2AuthenticatedPrincipal introspect(String token) {
|
||||||
RequestEntity<?> requestEntity = this.requestEntityConverter.convert(token);
|
RequestEntity<?> requestEntity = this.requestEntityConverter.convert(token);
|
||||||
if (requestEntity == null) {
|
if (requestEntity == null) {
|
||||||
throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active");
|
throw new OAuth2IntrospectionException("Provided token [" + token + "] isn't active");
|
||||||
@ -189,7 +197,8 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
return (TokenIntrospectionSuccessResponse) introspectionResponse;
|
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();
|
Map<String, Object> claims = response.toJSONObject();
|
||||||
if (response.getAudience() != null) {
|
if (response.getAudience() != null) {
|
||||||
List<String> audiences = new ArrayList<>();
|
List<String> audiences = new ArrayList<>();
|
||||||
@ -216,10 +225,15 @@ public class NimbusOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
|
|||||||
claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant());
|
claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant());
|
||||||
}
|
}
|
||||||
if (response.getScope() != null) {
|
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) {
|
private URL issuer(String uri) {
|
||||||
|
@ -19,6 +19,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -34,6 +36,10 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.MediaType;
|
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.util.Assert;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.reactive.function.client.ClientResponse;
|
import org.springframework.web.reactive.function.client.ClientResponse;
|
||||||
@ -59,8 +65,10 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
private URI introspectionUri;
|
private URI introspectionUri;
|
||||||
private WebClient webClient;
|
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 introspectionUri The introspection endpoint uri
|
||||||
* @param clientId The client id authorized to introspect
|
* @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 introspectionUri The introspection endpoint uri
|
||||||
* @param webClient The client for performing the introspection request
|
* @param webClient The client for performing the introspection request
|
||||||
@ -95,7 +103,7 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Mono<Map<String, Object>> introspect(String token) {
|
public Mono<OAuth2AuthenticatedPrincipal> introspect(String token) {
|
||||||
return Mono.just(token)
|
return Mono.just(token)
|
||||||
.flatMap(this::makeRequest)
|
.flatMap(this::makeRequest)
|
||||||
.flatMap(this::adaptToNimbusResponse)
|
.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();
|
Map<String, Object> claims = response.toJSONObject();
|
||||||
|
Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||||||
if (response.getAudience() != null) {
|
if (response.getAudience() != null) {
|
||||||
List<String> audiences = new ArrayList<>();
|
List<String> audiences = new ArrayList<>();
|
||||||
for (Audience audience : response.getAudience()) {
|
for (Audience audience : response.getAudience()) {
|
||||||
@ -177,10 +186,15 @@ public class NimbusReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
|
|||||||
claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant());
|
claims.put(NOT_BEFORE, response.getNotBeforeTime().toInstant());
|
||||||
}
|
}
|
||||||
if (response.getScope() != null) {
|
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) {
|
private URL issuer(String uri) {
|
||||||
|
@ -18,6 +18,8 @@ package org.springframework.security.oauth2.server.resource.introspection;
|
|||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A contract for introspecting and verifying an OAuth 2.0 token.
|
* A contract for introspecting and verifying an OAuth 2.0 token.
|
||||||
*
|
*
|
||||||
@ -41,5 +43,5 @@ public interface OpaqueTokenIntrospector {
|
|||||||
* @param token the token to introspect
|
* @param token the token to introspect
|
||||||
* @return the token's attributes
|
* @return the token's attributes
|
||||||
*/
|
*/
|
||||||
Map<String, Object> introspect(String token);
|
OAuth2AuthenticatedPrincipal introspect(String token);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A contract for introspecting and verifying an OAuth 2.0 token.
|
* A contract for introspecting and verifying an OAuth 2.0 token.
|
||||||
*
|
*
|
||||||
@ -43,5 +45,5 @@ public interface ReactiveOpaqueTokenIntrospector {
|
|||||||
* @param token the token to introspect
|
* @param token the token to introspect
|
||||||
* @return the token's attributes
|
* @return the token's attributes
|
||||||
*/
|
*/
|
||||||
Mono<Map<String, Object>> introspect(String token);
|
Mono<OAuth2AuthenticatedPrincipal> introspect(String token);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,23 +18,26 @@ package org.springframework.security.oauth2.server.resource.authentication;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
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.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.OAuth2IntrospectionClaimNames;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
|
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.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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
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.ACTIVE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE;
|
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT;
|
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.SCOPE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT;
|
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.OAuth2IntrospectionClaimNames.USERNAME;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.TestOAuth2TokenIntrospectionClientResponses.active;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link OAuth2IntrospectionAuthenticationProvider}
|
* Tests for {@link OpaqueTokenAuthenticationProvider}
|
||||||
*
|
*
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
* @since 5.2
|
|
||||||
*/
|
*/
|
||||||
public class OAuth2IntrospectionAuthenticationProviderTests {
|
public class OpaqueTokenAuthenticationProviderTests {
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenActiveTokenThenOk() throws Exception {
|
public void authenticateWhenActiveTokenThenOk() throws Exception {
|
||||||
Map<String, Object> claims = active();
|
OAuth2AuthenticatedPrincipal principal = active(attributes -> attributes.put("extension_field", "twenty-seven"));
|
||||||
claims.put("extension_field", "twenty-seven");
|
OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class);
|
||||||
OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class);
|
when(introspector.introspect(any())).thenReturn(principal);
|
||||||
when(introspectionClient.introspect(any())).thenReturn(claims);
|
OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector);
|
||||||
OAuth2IntrospectionAuthenticationProvider provider =
|
|
||||||
new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
|
|
||||||
|
|
||||||
Authentication result =
|
Authentication result =
|
||||||
provider.authenticate(new BearerTokenAuthenticationToken("token"));
|
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)
|
assertThat(attributes)
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(ACTIVE, true)
|
.containsEntry(ACTIVE, true)
|
||||||
@ -86,18 +85,16 @@ public class OAuth2IntrospectionAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
|
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
|
||||||
Map<String, Object> claims = active();
|
OAuth2AuthenticatedPrincipal principal = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null);
|
||||||
claims.remove(SCOPE);
|
OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class);
|
||||||
OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class);
|
when(introspector.introspect(any())).thenReturn(principal);
|
||||||
when(introspectionClient.introspect(any())).thenReturn(claims);
|
OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector);
|
||||||
OAuth2IntrospectionAuthenticationProvider provider =
|
|
||||||
new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
|
|
||||||
|
|
||||||
Authentication result =
|
Authentication result =
|
||||||
provider.authenticate(new BearerTokenAuthenticationToken("token"));
|
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)
|
assertThat(attributes)
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.doesNotContainKey(SCOPE);
|
.doesNotContainKey(SCOPE);
|
||||||
@ -107,10 +104,9 @@ public class OAuth2IntrospectionAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() {
|
public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() {
|
||||||
OpaqueTokenIntrospector introspectionClient = mock(OpaqueTokenIntrospector.class);
|
OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class);
|
||||||
when(introspectionClient.introspect(any())).thenThrow(new OAuth2IntrospectionException("with \"invalid\" chars"));
|
when(introspector.introspect(any())).thenThrow(new OAuth2IntrospectionException("with \"invalid\" chars"));
|
||||||
OAuth2IntrospectionAuthenticationProvider provider =
|
OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector);
|
||||||
new OAuth2IntrospectionAuthenticationProvider(introspectionClient);
|
|
||||||
|
|
||||||
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")))
|
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")))
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
@ -120,7 +116,7 @@ public class OAuth2IntrospectionAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() {
|
public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() {
|
||||||
assertThatCode(() -> new OAuth2IntrospectionAuthenticationProvider(null))
|
assertThatCode(() -> new OpaqueTokenAuthenticationProvider(null))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,24 +19,27 @@ package org.springframework.security.oauth2.server.resource.authentication;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenAttributes;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.security.core.Authentication;
|
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.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.OAuth2IntrospectionClaimNames;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionException;
|
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.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.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
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.ACTIVE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE;
|
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.AUDIENCE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.EXPIRES_AT;
|
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.SCOPE;
|
||||||
import static org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionClaimNames.SUBJECT;
|
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.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
|
@Test
|
||||||
public void authenticateWhenActiveTokenThenOk() throws Exception {
|
public void authenticateWhenActiveTokenThenOk() throws Exception {
|
||||||
Map<String, Object> claims = active();
|
OAuth2AuthenticatedPrincipal authority = active(attributes -> attributes.put("extension_field", "twenty-seven"));
|
||||||
claims.put("extension_field", "twenty-seven");
|
ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class);
|
||||||
ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class);
|
when(introspector.introspect(any())).thenReturn(Mono.just(authority));
|
||||||
when(introspectionClient.introspect(any())).thenReturn(Mono.just(claims));
|
OpaqueTokenReactiveAuthenticationManager provider =
|
||||||
OAuth2IntrospectionReactiveAuthenticationManager provider =
|
new OpaqueTokenReactiveAuthenticationManager(introspector);
|
||||||
new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient);
|
|
||||||
|
|
||||||
Authentication result =
|
Authentication result =
|
||||||
provider.authenticate(new BearerTokenAuthenticationToken("token")).block();
|
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)
|
assertThat(attributes)
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(ACTIVE, true)
|
.containsEntry(ACTIVE, true)
|
||||||
@ -85,18 +88,17 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
|
public void authenticateWhenMissingScopeAttributeThenNoAuthorities() {
|
||||||
Map<String, Object> claims = active();
|
OAuth2AuthenticatedPrincipal authority = new DefaultOAuth2AuthenticatedPrincipal(Collections.singletonMap("claim", "value"), null);
|
||||||
claims.remove(SCOPE);
|
ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class);
|
||||||
ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class);
|
when(introspector.introspect(any())).thenReturn(Mono.just(authority));
|
||||||
when(introspectionClient.introspect(any())).thenReturn(Mono.just(claims));
|
OpaqueTokenReactiveAuthenticationManager provider =
|
||||||
OAuth2IntrospectionReactiveAuthenticationManager provider =
|
new OpaqueTokenReactiveAuthenticationManager(introspector);
|
||||||
new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient);
|
|
||||||
|
|
||||||
Authentication result =
|
Authentication result =
|
||||||
provider.authenticate(new BearerTokenAuthenticationToken("token")).block();
|
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)
|
assertThat(attributes)
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.doesNotContainKey(SCOPE);
|
.doesNotContainKey(SCOPE);
|
||||||
@ -106,11 +108,11 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() {
|
public void authenticateWhenIntrospectionEndpointThrowsExceptionThenInvalidToken() {
|
||||||
ReactiveOpaqueTokenIntrospector introspectionClient = mock(ReactiveOpaqueTokenIntrospector.class);
|
ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class);
|
||||||
when(introspectionClient.introspect(any()))
|
when(introspector.introspect(any()))
|
||||||
.thenReturn(Mono.error(new OAuth2IntrospectionException("with \"invalid\" chars")));
|
.thenReturn(Mono.error(new OAuth2IntrospectionException("with \"invalid\" chars")));
|
||||||
OAuth2IntrospectionReactiveAuthenticationManager provider =
|
OpaqueTokenReactiveAuthenticationManager provider =
|
||||||
new OAuth2IntrospectionReactiveAuthenticationManager(introspectionClient);
|
new OpaqueTokenReactiveAuthenticationManager(introspector);
|
||||||
|
|
||||||
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")).block())
|
assertThatCode(() -> provider.authenticate(new BearerTokenAuthenticationToken("token")).block())
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
@ -120,7 +122,7 @@ public class OAuth2IntrospectionReactiveAuthenticationManagerTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() {
|
public void constructorWhenIntrospectionClientIsNullThenIllegalArgumentException() {
|
||||||
assertThatCode(() -> new OAuth2IntrospectionReactiveAuthenticationManager(null))
|
assertThatCode(() -> new OpaqueTokenReactiveAuthenticationManager(null))
|
||||||
.isInstanceOf(IllegalArgumentException.class);
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -38,6 +38,7 @@ import org.springframework.http.HttpStatus;
|
|||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.web.client.RestOperations;
|
import org.springframework.web.client.RestOperations;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
@ -113,8 +114,8 @@ public class NimbusOpaqueTokenIntrospectorTests {
|
|||||||
OpaqueTokenIntrospector introspectionClient =
|
OpaqueTokenIntrospector introspectionClient =
|
||||||
new NimbusOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET);
|
new NimbusOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET);
|
||||||
|
|
||||||
Map<String, Object> attributes = introspectionClient.introspect("token");
|
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token");
|
||||||
assertThat(attributes)
|
assertThat(authority.getAttributes())
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
||||||
.containsEntry(AUDIENCE, Arrays.asList("https://protected.example.net/resource"))
|
.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)))
|
when(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
|
||||||
.thenReturn(response(new JSONObject(introspectedValues).toJSONString()));
|
.thenReturn(response(new JSONObject(introspectedValues).toJSONString()));
|
||||||
|
|
||||||
Map<String, Object> attributes = introspectionClient.introspect("token");
|
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token");
|
||||||
assertThat(attributes)
|
assertThat(authority.getAttributes())
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
||||||
.containsEntry(AUDIENCE, Arrays.asList("aud"))
|
.containsEntry(AUDIENCE, Arrays.asList("aud"))
|
||||||
|
@ -36,6 +36,7 @@ import reactor.core.publisher.Mono;
|
|||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
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.ClientResponse;
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
@ -103,8 +104,8 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests {
|
|||||||
NimbusReactiveOpaqueTokenIntrospector introspectionClient =
|
NimbusReactiveOpaqueTokenIntrospector introspectionClient =
|
||||||
new NimbusReactiveOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET);
|
new NimbusReactiveOpaqueTokenIntrospector(introspectUri, CLIENT_ID, CLIENT_SECRET);
|
||||||
|
|
||||||
Map<String, Object> attributes = introspectionClient.introspect("token").block();
|
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block();
|
||||||
assertThat(attributes)
|
assertThat(authority.getAttributes())
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
||||||
.containsEntry(AUDIENCE, Arrays.asList("https://protected.example.net/resource"))
|
.containsEntry(AUDIENCE, Arrays.asList("https://protected.example.net/resource"))
|
||||||
@ -155,8 +156,8 @@ public class NimbusReactiveOpaqueTokenIntrospectorTests {
|
|||||||
NimbusReactiveOpaqueTokenIntrospector introspectionClient =
|
NimbusReactiveOpaqueTokenIntrospector introspectionClient =
|
||||||
new NimbusReactiveOpaqueTokenIntrospector(INTROSPECTION_URL, webClient);
|
new NimbusReactiveOpaqueTokenIntrospector(INTROSPECTION_URL, webClient);
|
||||||
|
|
||||||
Map<String, Object> attributes = introspectionClient.introspect("token").block();
|
OAuth2AuthenticatedPrincipal authority = introspectionClient.introspect("token").block();
|
||||||
assertThat(attributes)
|
assertThat(authority.getAttributes())
|
||||||
.isNotNull()
|
.isNotNull()
|
||||||
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
.containsEntry(OAuth2IntrospectionClaimNames.ACTIVE, true)
|
||||||
.containsEntry(AUDIENCE, Arrays.asList("aud"))
|
.containsEntry(AUDIENCE, Arrays.asList("aud"))
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,7 +16,7 @@
|
|||||||
package sample;
|
package sample;
|
||||||
|
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
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.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@ -27,8 +27,8 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
public class OAuth2ResourceServerController {
|
public class OAuth2ResourceServerController {
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String index(@AuthenticationPrincipal OAuth2TokenAttributes attributes) {
|
public String index(@AuthenticationPrincipal OAuth2AuthenticatedPrincipal principal) {
|
||||||
return String.format("Hello, %s!", (String) attributes.getAttribute("sub"));
|
return String.format("Hello, %s!", (String) principal.getAttribute("sub"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/message")
|
@GetMapping("/message")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user