mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-01 09:42:13 +00:00
Add authentication converter for introspected tokens
Adds configurable authentication converter for resource-servers with token introspection (something very similar to what JwtAuthenticationConverter does for resource-servers with JWT decoder). The new (Reactive)OpaqueTokenAuthenticationConverter is given responsibility for converting successful token introspection result into an Authentication instance (which is currently done by a private methods of OpaqueTokenAuthenticationProvider and OpaqueTokenReactiveAuthenticationManager). The default (Reactive)OpaqueTokenAuthenticationConverter, behave the same as current private convert(OAuth2AuthenticatedPrincipal principal, String token) methods: map authorities from scope attribute and build a BearerTokenAuthentication. Closes gh-11661
This commit is contained in:
parent
cc988fc287
commit
1efb63387f
@ -21,10 +21,12 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@ -46,6 +48,7 @@ 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.OpaqueTokenAuthenticationProvider;
|
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
|
||||||
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.introspection.SpringOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||||
@ -107,8 +110,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its
|
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint with its
|
||||||
* authentication configuration
|
* client credentials and an OpaqueTokenAuthenticationConverter
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <h2>Security Filters</h2>
|
* <h2>Security Filters</h2>
|
||||||
@ -138,6 +141,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||||||
*
|
*
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
* @author Evgeniy Cheban
|
* @author Evgeniy Cheban
|
||||||
|
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
* @see BearerTokenAuthenticationFilter
|
* @see BearerTokenAuthenticationFilter
|
||||||
* @see JwtAuthenticationProvider
|
* @see JwtAuthenticationProvider
|
||||||
@ -456,6 +460,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
|
|
||||||
private Supplier<OpaqueTokenIntrospector> introspector;
|
private Supplier<OpaqueTokenIntrospector> introspector;
|
||||||
|
|
||||||
|
private Supplier<OpaqueTokenAuthenticationConverter> authenticationConverter;
|
||||||
|
|
||||||
OpaqueTokenConfigurer(ApplicationContext context) {
|
OpaqueTokenConfigurer(ApplicationContext context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
@ -490,6 +496,13 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OpaqueTokenConfigurer authenticationConverter(
|
||||||
|
OpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||||
|
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||||
|
this.authenticationConverter = () -> authenticationConverter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
OpaqueTokenIntrospector getIntrospector() {
|
OpaqueTokenIntrospector getIntrospector() {
|
||||||
if (this.introspector != null) {
|
if (this.introspector != null) {
|
||||||
return this.introspector.get();
|
return this.introspector.get();
|
||||||
@ -497,12 +510,27 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||||||
return this.context.getBean(OpaqueTokenIntrospector.class);
|
return this.context.getBean(OpaqueTokenIntrospector.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Optional<OpaqueTokenAuthenticationConverter> getAuthenticationConverter() {
|
||||||
|
if (this.authenticationConverter != null) {
|
||||||
|
return Optional.of(this.authenticationConverter.get());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Optional.of(this.context.getBean(OpaqueTokenAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException nsbde) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AuthenticationProvider getAuthenticationProvider() {
|
AuthenticationProvider getAuthenticationProvider() {
|
||||||
if (this.authenticationManager != null) {
|
if (this.authenticationManager != null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
OpaqueTokenIntrospector introspector = getIntrospector();
|
OpaqueTokenIntrospector introspector = getIntrospector();
|
||||||
return new OpaqueTokenAuthenticationProvider(introspector);
|
final OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
|
||||||
|
introspector);
|
||||||
|
getAuthenticationConverter().ifPresent(opaqueTokenAuthenticationProvider::setAuthenticationConverter);
|
||||||
|
return opaqueTokenAuthenticationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManager getAuthenticationManager(H http) {
|
AuthenticationManager getAuthenticationManager(H http) {
|
||||||
|
@ -251,6 +251,9 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||||||
|
|
||||||
static final String CLIENT_SECRET = "client-secret";
|
static final String CLIENT_SECRET = "client-secret";
|
||||||
|
|
||||||
|
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
|
||||||
|
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
|
||||||
|
|
||||||
OpaqueTokenBeanDefinitionParser() {
|
OpaqueTokenBeanDefinitionParser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,9 +261,14 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||||
validateConfiguration(element, pc);
|
validateConfiguration(element, pc);
|
||||||
BeanMetadataElement introspector = getIntrospector(element);
|
BeanMetadataElement introspector = getIntrospector(element);
|
||||||
|
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
|
||||||
BeanDefinitionBuilder opaqueTokenProviderBuilder = BeanDefinitionBuilder
|
BeanDefinitionBuilder opaqueTokenProviderBuilder = BeanDefinitionBuilder
|
||||||
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
|
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
|
||||||
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
|
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
|
||||||
|
if (StringUtils.hasText(authenticationConverterRef)) {
|
||||||
|
opaqueTokenProviderBuilder.addPropertyValue(AUTHENTICATION_CONVERTER,
|
||||||
|
new RuntimeBeanReference(authenticationConverterRef));
|
||||||
|
}
|
||||||
return opaqueTokenProviderBuilder.getBeanDefinition();
|
return opaqueTokenProviderBuilder.getBeanDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
@ -35,6 +36,7 @@ import reactor.core.publisher.Mono;
|
|||||||
import reactor.util.context.Context;
|
import reactor.util.context.Context;
|
||||||
|
|
||||||
import org.springframework.beans.BeansException;
|
import org.springframework.beans.BeansException;
|
||||||
|
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.core.Ordered;
|
import org.springframework.core.Ordered;
|
||||||
import org.springframework.core.ResolvableType;
|
import org.springframework.core.ResolvableType;
|
||||||
@ -95,6 +97,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtRea
|
|||||||
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
|
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
|
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
|
||||||
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.ReactiveOpaqueTokenAuthenticationConverter;
|
||||||
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.web.access.server.BearerTokenServerAccessDeniedHandler;
|
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
|
||||||
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
|
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
|
||||||
@ -4283,6 +4286,8 @@ public class ServerHttpSecurity {
|
|||||||
|
|
||||||
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
|
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
|
||||||
|
|
||||||
|
private Supplier<ReactiveOpaqueTokenAuthenticationConverter> authenticationConverter;
|
||||||
|
|
||||||
private OpaqueTokenSpec() {
|
private OpaqueTokenSpec() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4321,6 +4326,13 @@ public class ServerHttpSecurity {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OpaqueTokenSpec authenticationConverter(
|
||||||
|
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||||
|
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||||
|
this.authenticationConverter = () -> authenticationConverter;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows method chaining to continue configuring the
|
* Allows method chaining to continue configuring the
|
||||||
* {@link ServerHttpSecurity}
|
* {@link ServerHttpSecurity}
|
||||||
@ -4331,7 +4343,11 @@ public class ServerHttpSecurity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
||||||
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
|
final OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
|
||||||
|
getIntrospector());
|
||||||
|
Optional.ofNullable(getAuthenticationConverter())
|
||||||
|
.ifPresent(authenticationManager::setAuthenticationConverter);
|
||||||
|
return authenticationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
|
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
|
||||||
@ -4341,6 +4357,18 @@ public class ServerHttpSecurity {
|
|||||||
return getBean(ReactiveOpaqueTokenIntrospector.class);
|
return getBean(ReactiveOpaqueTokenIntrospector.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() {
|
||||||
|
if (this.authenticationConverter != null) {
|
||||||
|
return this.authenticationConverter.get();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return getBean(ReactiveOpaqueTokenAuthenticationConverter.class);
|
||||||
|
}
|
||||||
|
catch (NoSuchBeanDefinitionException nsbde) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void configure(ServerHttpSecurity http) {
|
protected void configure(ServerHttpSecurity http) {
|
||||||
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
|
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
|
||||||
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
|
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package org.springframework.security.config.web.server
|
package org.springframework.security.config.web.server
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,6 +31,7 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv
|
|||||||
class ServerOpaqueTokenDsl {
|
class ServerOpaqueTokenDsl {
|
||||||
private var _introspectionUri: String? = null
|
private var _introspectionUri: String? = null
|
||||||
private var _introspector: ReactiveOpaqueTokenIntrospector? = null
|
private var _introspector: ReactiveOpaqueTokenIntrospector? = null
|
||||||
|
private var _authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter? = null
|
||||||
private var clientCredentials: Pair<String, String>? = null
|
private var clientCredentials: Pair<String, String>? = null
|
||||||
|
|
||||||
var introspectionUri: String?
|
var introspectionUri: String?
|
||||||
@ -37,14 +39,21 @@ class ServerOpaqueTokenDsl {
|
|||||||
set(value) {
|
set(value) {
|
||||||
_introspectionUri = value
|
_introspectionUri = value
|
||||||
_introspector = null
|
_introspector = null
|
||||||
|
_authenticationConverter = null
|
||||||
}
|
}
|
||||||
var introspector: ReactiveOpaqueTokenIntrospector?
|
var introspector: ReactiveOpaqueTokenIntrospector?
|
||||||
get() = _introspector
|
get() = _introspector
|
||||||
set(value) {
|
set(value) {
|
||||||
_introspector = value
|
_introspector = value
|
||||||
|
_authenticationConverter = null
|
||||||
_introspectionUri = null
|
_introspectionUri = null
|
||||||
clientCredentials = null
|
clientCredentials = null
|
||||||
}
|
}
|
||||||
|
var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter?
|
||||||
|
get() = _authenticationConverter
|
||||||
|
set(value) {
|
||||||
|
_authenticationConverter = value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the credentials for Introspection endpoint.
|
* Configures the credentials for Introspection endpoint.
|
||||||
@ -55,6 +64,7 @@ class ServerOpaqueTokenDsl {
|
|||||||
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
|
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
|
||||||
clientCredentials = Pair(clientId, clientSecret)
|
clientCredentials = Pair(clientId, clientSecret)
|
||||||
_introspector = null
|
_introspector = null
|
||||||
|
_authenticationConverter = null
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
|
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
|
||||||
@ -62,6 +72,7 @@ class ServerOpaqueTokenDsl {
|
|||||||
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
||||||
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
||||||
introspector?.also { opaqueToken.introspector(introspector) }
|
introspector?.also { opaqueToken.introspector(introspector) }
|
||||||
|
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import org.springframework.security.authentication.AuthenticationManager
|
|||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||||
import org.springframework.security.core.Authentication
|
import org.springframework.security.core.Authentication
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +38,7 @@ class OpaqueTokenDsl {
|
|||||||
private var _introspectionUri: String? = null
|
private var _introspectionUri: String? = null
|
||||||
private var _introspector: OpaqueTokenIntrospector? = null
|
private var _introspector: OpaqueTokenIntrospector? = null
|
||||||
private var clientCredentials: Pair<String, String>? = null
|
private var clientCredentials: Pair<String, String>? = null
|
||||||
|
private var _authenticationConverter: OpaqueTokenAuthenticationConverter? = null
|
||||||
|
|
||||||
var authenticationManager: AuthenticationManager? = null
|
var authenticationManager: AuthenticationManager? = null
|
||||||
|
|
||||||
@ -54,6 +56,11 @@ class OpaqueTokenDsl {
|
|||||||
clientCredentials = null
|
clientCredentials = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var authenticationConverter: OpaqueTokenAuthenticationConverter?
|
||||||
|
get() = _authenticationConverter
|
||||||
|
set(value) {
|
||||||
|
_authenticationConverter = value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the credentials for Introspection endpoint.
|
* Configures the credentials for Introspection endpoint.
|
||||||
@ -70,6 +77,7 @@ class OpaqueTokenDsl {
|
|||||||
return { opaqueToken ->
|
return { opaqueToken ->
|
||||||
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
||||||
introspector?.also { opaqueToken.introspector(introspector) }
|
introspector?.also { opaqueToken.introspector(introspector) }
|
||||||
|
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
|
||||||
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
||||||
authenticationManager?.also { opaqueToken.authenticationManager(authenticationManager) }
|
authenticationManager?.also { opaqueToken.authenticationManager(authenticationManager) }
|
||||||
}
|
}
|
||||||
|
@ -667,6 +667,9 @@ opaque-token.attlist &=
|
|||||||
opaque-token.attlist &=
|
opaque-token.attlist &=
|
||||||
## Reference to an OpaqueTokenIntrospector
|
## Reference to an OpaqueTokenIntrospector
|
||||||
attribute introspector-ref {xsd:token}?
|
attribute introspector-ref {xsd:token}?
|
||||||
|
opaque-token.attlist &=
|
||||||
|
## Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful introspection result into an Authentication.
|
||||||
|
attribute authentication-converter-ref {xsd:token}?
|
||||||
|
|
||||||
openid-login =
|
openid-login =
|
||||||
## Sets up form login for authentication with an Open ID identity. NOTE: The OpenID 1.0 and 2.0 protocols have been deprecated and users are <a href="https://openid.net/specs/openid-connect-migration-1_0.html">encouraged to migrate</a> to <a href="https://openid.net/connect/">OpenID Connect</a>, which is supported by <code>spring-security-oauth2</code>.
|
## Sets up form login for authentication with an Open ID identity. NOTE: The OpenID 1.0 and 2.0 protocols have been deprecated and users are <a href="https://openid.net/specs/openid-connect-migration-1_0.html">encouraged to migrate</a> to <a href="https://openid.net/connect/">OpenID Connect</a>, which is supported by <code>spring-security-oauth2</code>.
|
||||||
|
@ -2060,6 +2060,13 @@
|
|||||||
</xs:documentation>
|
</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="authentication-converter-ref" type="xs:token">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful
|
||||||
|
introspection result into an Authentication.
|
||||||
|
</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
</xs:attributeGroup>
|
</xs:attributeGroup>
|
||||||
|
|
||||||
<xs:element name="attribute-exchange">
|
<xs:element name="attribute-exchange">
|
||||||
|
@ -23,6 +23,7 @@ import java.security.interfaces.RSAPublicKey;
|
|||||||
import java.time.Clock;
|
import java.time.Clock;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -68,13 +69,17 @@ 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.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.JwtBeanDefinitionParser;
|
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.JwtBeanDefinitionParser;
|
||||||
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser;
|
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser;
|
||||||
import org.springframework.security.config.test.SpringTestContext;
|
import org.springframework.security.config.test.SpringTestContext;
|
||||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
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;
|
||||||
@ -87,6 +92,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|||||||
import org.springframework.security.oauth2.jwt.TestJwts;
|
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||||
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.introspection.NimbusOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
|
||||||
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.BearerTokenResolver;
|
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
|
||||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||||
@ -662,6 +668,20 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void configureWhenIntrospectingWithAuthenticationConverterThenUses() throws Exception {
|
||||||
|
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueTokenAndAuthenticationConverter"))
|
||||||
|
.autowire();
|
||||||
|
mockRestOperations(json("Active"));
|
||||||
|
// @formatter:off
|
||||||
|
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token"))
|
||||||
|
.andExpect(status().isNotFound());
|
||||||
|
|
||||||
|
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalidToken"))
|
||||||
|
.andExpect(status().isUnauthorized());
|
||||||
|
// @formatter:on
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
|
public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
|
||||||
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
|
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
|
||||||
@ -1096,4 +1116,39 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class TestAuthentication extends AbstractAuthenticationToken {
|
||||||
|
|
||||||
|
private final String introspectedToken;
|
||||||
|
|
||||||
|
public TestAuthentication(String introspectedToken, Collection<? extends GrantedAuthority> authorities) {
|
||||||
|
super(authorities);
|
||||||
|
this.introspectedToken = introspectedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return this.introspectedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return this.introspectedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return "token".equals(this.introspectedToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestOpaqueTokenAuthenticationConverter implements OpaqueTokenAuthenticationConverter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||||
|
return new TestAuthentication(introspectedToken, Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2002-2020 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://www.springframework.org/schema/security"
|
||||||
|
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||||
|
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||||
|
|
||||||
|
<b:bean name="authentication-converter"
|
||||||
|
class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$TestOpaqueTokenAuthenticationConverter">
|
||||||
|
</b:bean>
|
||||||
|
|
||||||
|
<http>
|
||||||
|
<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
|
||||||
|
<intercept-url pattern="/**" access="authenticated"/>
|
||||||
|
<oauth2-resource-server>
|
||||||
|
<opaque-token introspector-ref="introspector" authentication-converter-ref="authentication-converter"/>
|
||||||
|
</oauth2-resource-server>
|
||||||
|
</http>
|
||||||
|
|
||||||
|
<b:import resource="userservice.xml"/>
|
||||||
|
</b:beans>
|
@ -1325,6 +1325,10 @@ The Client Id to use for client authentication against the provided `introspecti
|
|||||||
* **client-secret**
|
* **client-secret**
|
||||||
The Client Secret to use for client authentication against the provided `introspection-uri`.
|
The Client Secret to use for client authentication against the provided `introspection-uri`.
|
||||||
|
|
||||||
|
[[nsa-opaque-token-authentication-converter-ref]]
|
||||||
|
* **authentication-converter-ref**
|
||||||
|
Reference to an `OpaqueTokenAuthenticationConverter`. Responsible for converting successful introspection result into an `Authentication` instance.
|
||||||
|
|
||||||
|
|
||||||
[[nsa-relying-party-registrations]]
|
[[nsa-relying-party-registrations]]
|
||||||
== <relying-party-registrations>
|
== <relying-party-registrations>
|
||||||
|
@ -295,11 +295,13 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||||||
----
|
----
|
||||||
====
|
====
|
||||||
|
|
||||||
If the application doesn't expose a <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> bean, then Spring Boot will expose the above default one.
|
If the application doesn't expose an <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> bean, then Spring Boot will expose the above default one.
|
||||||
|
|
||||||
And its configuration can be overridden using `introspectionUri()` and `introspectionClientCredentials()` or replaced using `introspector()`.
|
And its configuration can be overridden using `introspectionUri()` and `introspectionClientCredentials()` or replaced using `introspector()`.
|
||||||
|
|
||||||
Or, if you're not using Spring Boot at all, then both of these components - the filter chain and a <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> can be specified in XML.
|
If the application doesn't expose an `OpaqueTokenAuthenticationConverter` bean, then spring-security will build `BearerTokenAuthentication`.
|
||||||
|
|
||||||
|
Or, if you're not using Spring Boot at all, then all of these components - the filter chain, an <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> and an `OpaqueTokenAuthenticationConverter` can be specified in XML.
|
||||||
|
|
||||||
The filter chain is specified like so:
|
The filter chain is specified like so:
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
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.Collection;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
@ -25,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
|
|||||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
|
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||||
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;
|
||||||
@ -35,6 +35,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenAuthentica
|
|||||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
||||||
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.OpaqueTokenAuthenticationConverter;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -49,16 +50,21 @@ import org.springframework.util.Assert;
|
|||||||
* opaque access token, returning its attributes set as part of the {@link Authentication}
|
* opaque access token, returning its attributes set as part of the {@link Authentication}
|
||||||
* statement.
|
* statement.
|
||||||
* <p>
|
* <p>
|
||||||
* Scopes are translated into {@link GrantedAuthority}s according to the following
|
* This {@link ReactiveAuthenticationManager} is responsible for introspecting and
|
||||||
* algorithm:
|
* verifying an opaque access token, returning its attributes set as part of the
|
||||||
* <ol>
|
* {@link Authentication} statement.
|
||||||
* <li>If there is a "scope" attribute, then convert to a {@link Collection} of
|
* <p>
|
||||||
* {@link String}s.
|
* <p>
|
||||||
* <li>Take the resulting {@link Collection} and prepend the "SCOPE_" keyword to each
|
* {@link org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector}
|
||||||
* element, adding as {@link GrantedAuthority}s.
|
* is responsible for retrieving token attributes from authorization-server.
|
||||||
* </ol>
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* authenticationConverter is responsible for turning successful introspection into
|
||||||
|
* {@link Authentication} (which includes {@link GrantedAuthority}s mapping from token
|
||||||
|
* attributes or retrieving from an other source)
|
||||||
*
|
*
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
|
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see AuthenticationProvider
|
* @see AuthenticationProvider
|
||||||
*/
|
*/
|
||||||
@ -68,6 +74,8 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
|
|
||||||
private final OpaqueTokenIntrospector introspector;
|
private final OpaqueTokenIntrospector introspector;
|
||||||
|
|
||||||
|
private OpaqueTokenAuthenticationConverter authenticationConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters
|
* Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters
|
||||||
* @param introspector The {@link OpaqueTokenIntrospector} to use
|
* @param introspector The {@link OpaqueTokenIntrospector} to use
|
||||||
@ -75,12 +83,20 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
public OpaqueTokenAuthenticationProvider(OpaqueTokenIntrospector introspector) {
|
public OpaqueTokenAuthenticationProvider(OpaqueTokenIntrospector introspector) {
|
||||||
Assert.notNull(introspector, "introspector cannot be null");
|
Assert.notNull(introspector, "introspector cannot be null");
|
||||||
this.introspector = introspector;
|
this.introspector = introspector;
|
||||||
|
this.setAuthenticationConverter(OpaqueTokenAuthenticationProvider::convert);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* <p>
|
||||||
* Introspect and validate the opaque
|
* Introspect and validate the opaque
|
||||||
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
||||||
* Token</a>.
|
* Token</a> and then delegates {@link Authentication} instantiation to
|
||||||
|
* {@link OpaqueTokenAuthenticationConverter}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||||
|
* details are null, then introspection result details are used.
|
||||||
|
* </p>
|
||||||
* @param authentication the authentication request object.
|
* @param authentication the authentication request object.
|
||||||
* @return A successful authentication
|
* @return A successful authentication
|
||||||
* @throws AuthenticationException if authentication failed for some reason
|
* @throws AuthenticationException if authentication failed for some reason
|
||||||
@ -92,8 +108,16 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
}
|
}
|
||||||
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
|
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
|
||||||
OAuth2AuthenticatedPrincipal principal = getOAuth2AuthenticatedPrincipal(bearer);
|
OAuth2AuthenticatedPrincipal principal = getOAuth2AuthenticatedPrincipal(bearer);
|
||||||
AbstractAuthenticationToken result = convert(principal, bearer.getToken());
|
Authentication result = this.authenticationConverter.convert(bearer.getToken(), principal);
|
||||||
result.setDetails(bearer.getDetails());
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (AbstractAuthenticationToken.class.isAssignableFrom(result.getClass())) {
|
||||||
|
final AbstractAuthenticationToken auth = (AbstractAuthenticationToken) result;
|
||||||
|
if (auth.getDetails() == null) {
|
||||||
|
auth.setDetails(bearer.getDetails());
|
||||||
|
}
|
||||||
|
}
|
||||||
this.logger.debug("Authenticated token");
|
this.logger.debug("Authenticated token");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -116,11 +140,32 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||||||
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AbstractAuthenticationToken convert(OAuth2AuthenticatedPrincipal principal, String token) {
|
/**
|
||||||
Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
* Default {@link OpaqueTokenAuthenticationConverter}.
|
||||||
Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
* @param introspectedToken the bearer sring that was successfuly introspected
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
* @param authenticatedPrincipal the successful introspection output
|
||||||
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
* @returna {@link BearerTokenAuthentication}
|
||||||
|
*/
|
||||||
|
static BearerTokenAuthentication convert(String introspectedToken,
|
||||||
|
OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||||
|
Instant iat = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
||||||
|
Instant exp = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
||||||
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, introspectedToken,
|
||||||
|
iat, exp);
|
||||||
|
return new BearerTokenAuthentication(authenticatedPrincipal, accessToken,
|
||||||
|
authenticatedPrincipal.getAuthorities());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide with a custom bean to turn successful introspection result into an
|
||||||
|
* {@link Authentication} instance of your choice. By default,
|
||||||
|
* {@link BearerTokenAuthentication} will be built.
|
||||||
|
* @param authenticationConverter the converter to use
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public void setAuthenticationConverter(OpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||||
|
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||||
|
this.authenticationConverter = authenticationConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,28 +16,27 @@
|
|||||||
|
|
||||||
package org.springframework.security.oauth2.server.resource.authentication;
|
package org.springframework.security.oauth2.server.resource.authentication;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||||
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.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
|
|
||||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
||||||
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.OpaqueTokenAuthenticationConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link ReactiveAuthenticationManager} implementation for opaque
|
* An {@link ReactiveAuthenticationManager} implementation for opaque
|
||||||
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target= "_blank">Bearer
|
||||||
* Token</a>s, using an
|
* Token</a>s, using an
|
||||||
* <a href="https://tools.ietf.org/html/rfc7662" target="_blank">OAuth 2.0 Introspection
|
* <a href="https://tools.ietf.org/html/rfc7662" target="_blank">OAuth 2.0 Introspection
|
||||||
* Endpoint</a> to check the token's validity and reveal its attributes.
|
* Endpoint</a> to check the token's validity and reveal its attributes.
|
||||||
@ -46,16 +45,17 @@ import org.springframework.util.Assert;
|
|||||||
* verifying an opaque access token, returning its attributes set as part of the
|
* verifying an opaque access token, returning its attributes set as part of the
|
||||||
* {@link Authentication} statement.
|
* {@link Authentication} statement.
|
||||||
* <p>
|
* <p>
|
||||||
* Scopes are translated into {@link GrantedAuthority}s according to the following
|
* <p>
|
||||||
* algorithm:
|
* {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token attributes
|
||||||
* <ol>
|
* from authorization-server.
|
||||||
* <li>If there is a "scope" attribute, then convert to a {@link Collection} of
|
* </p>
|
||||||
* {@link String}s.
|
* <p>
|
||||||
* <li>Take the resulting {@link Collection} and prepend the "SCOPE_" keyword to each
|
* authenticationConverter is responsible for turning successful introspection into
|
||||||
* element, adding as {@link GrantedAuthority}s.
|
* {@link Authentication} (which includes {@link GrantedAuthority}s mapping from token
|
||||||
* </ol>
|
* attributes or retrieving from another source)
|
||||||
*
|
*
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
|
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @see ReactiveAuthenticationManager
|
* @see ReactiveAuthenticationManager
|
||||||
*/
|
*/
|
||||||
@ -63,6 +63,8 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||||||
|
|
||||||
private final ReactiveOpaqueTokenIntrospector introspector;
|
private final ReactiveOpaqueTokenIntrospector introspector;
|
||||||
|
|
||||||
|
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
|
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
|
||||||
* parameters
|
* parameters
|
||||||
@ -71,8 +73,23 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||||||
public OpaqueTokenReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspector) {
|
public OpaqueTokenReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspector) {
|
||||||
Assert.notNull(introspector, "introspector cannot be null");
|
Assert.notNull(introspector, "introspector cannot be null");
|
||||||
this.introspector = introspector;
|
this.introspector = introspector;
|
||||||
|
this.setAuthenticationConverter(OpaqueTokenReactiveAuthenticationManager::convert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Introspect and validate the opaque
|
||||||
|
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
||||||
|
* Token</a> and then delegates {@link Authentication} instantiation to
|
||||||
|
* {@link OpaqueTokenAuthenticationConverter}.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||||
|
* details are null, then introspection result details are used.
|
||||||
|
* </p>
|
||||||
|
* @param authentication the authentication request object.
|
||||||
|
* @return A successful authentication
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
@ -80,21 +97,14 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||||||
.filter(BearerTokenAuthenticationToken.class::isInstance)
|
.filter(BearerTokenAuthenticationToken.class::isInstance)
|
||||||
.cast(BearerTokenAuthenticationToken.class)
|
.cast(BearerTokenAuthenticationToken.class)
|
||||||
.map(BearerTokenAuthenticationToken::getToken)
|
.map(BearerTokenAuthenticationToken::getToken)
|
||||||
.flatMap(this::authenticate)
|
.flatMap(this::authenticate);
|
||||||
.cast(Authentication.class);
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<BearerTokenAuthentication> authenticate(String token) {
|
private Mono<Authentication> authenticate(String token) {
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
return this.introspector.introspect(token)
|
return this.introspector.introspect(token)
|
||||||
.map((principal) -> {
|
.flatMap((principal) -> this.authenticationConverter.convert(token, principal))
|
||||||
Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
|
||||||
Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
|
||||||
// construct token
|
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
|
||||||
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
|
||||||
})
|
|
||||||
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
|
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
}
|
}
|
||||||
@ -106,4 +116,27 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||||||
return new AuthenticationServiceException(ex.getMessage(), ex);
|
return new AuthenticationServiceException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default reactive {@link OpaqueTokenAuthenticationConverter}.
|
||||||
|
* @param introspectedToken the bearer sring that was successfuly introspected
|
||||||
|
* @param authenticatedPrincipal the successful introspection output
|
||||||
|
* @returna an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
|
||||||
|
* result
|
||||||
|
*/
|
||||||
|
static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||||
|
return Mono.just(OpaqueTokenAuthenticationProvider.convert(introspectedToken, authenticatedPrincipal));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide with a custom bean to turn successful introspection result into an
|
||||||
|
* {@link Authentication} instance of your choice. By default,
|
||||||
|
* {@link BearerTokenAuthentication} will be built.
|
||||||
|
* @param authenticationConverter the converter to use
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
public void setAuthenticationConverter(ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||||
|
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||||
|
this.authenticationConverter = authenticationConverter;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2022 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 org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn successful introspection result into an Authentication instance
|
||||||
|
*
|
||||||
|
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface OpaqueTokenAuthenticationConverter {
|
||||||
|
|
||||||
|
Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2021 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 reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn successful introspection result into an Authentication instance
|
||||||
|
*
|
||||||
|
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||||
|
* @since 5.8
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface ReactiveOpaqueTokenAuthenticationConverter {
|
||||||
|
|
||||||
|
Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal);
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2021 the original author or authors.
|
* Copyright 2002-2022 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user