Skip to content

Commit

Permalink
spring-projectsgh-11661 authentication converter for introspected tokens
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ch4mpy committed Sep 10, 2022
1 parent 5ae492b commit d2b2d66
Show file tree
Hide file tree
Showing 16 changed files with 385 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.MediaType;
Expand All @@ -46,6 +48,7 @@
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.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.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
Expand Down Expand Up @@ -107,8 +110,8 @@
* </ul>
*
* <p>
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its
* authentication configuration
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint with its
* client credentials and an OpaqueTokenAuthenticationConverter
* </p>
*
* <h2>Security Filters</h2>
Expand Down Expand Up @@ -138,6 +141,7 @@
*
* @author Josh Cummings
* @author Evgeniy Cheban
* @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt;
* @since 5.1
* @see BearerTokenAuthenticationFilter
* @see JwtAuthenticationProvider
Expand Down Expand Up @@ -456,6 +460,8 @@ public class OpaqueTokenConfigurer {

private Supplier<OpaqueTokenIntrospector> introspector;

private Supplier<OpaqueTokenAuthenticationConverter> authenticationConverter;

OpaqueTokenConfigurer(ApplicationContext context) {
this.context = context;
}
Expand Down Expand Up @@ -490,19 +496,41 @@ public OpaqueTokenConfigurer introspector(OpaqueTokenIntrospector introspector)
return this;
}

public OpaqueTokenConfigurer authenticationConverter(
OpaqueTokenAuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = () -> authenticationConverter;
return this;
}

OpaqueTokenIntrospector getIntrospector() {
if (this.introspector != null) {
return this.introspector.get();
}
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() {
if (this.authenticationManager != null) {
return null;
}
OpaqueTokenIntrospector introspector = getIntrospector();
return new OpaqueTokenAuthenticationProvider(introspector);
final OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
introspector);
getAuthenticationConverter().ifPresent(opaqueTokenAuthenticationProvider::setAuthenticationConverter);
return opaqueTokenAuthenticationProvider;
}

AuthenticationManager getAuthenticationManager(H http) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,16 +251,24 @@ static final class OpaqueTokenBeanDefinitionParser implements BeanDefinitionPars

static final String CLIENT_SECRET = "client-secret";

static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";

OpaqueTokenBeanDefinitionParser() {
}

@Override
public BeanDefinition parse(Element element, ParserContext pc) {
validateConfiguration(element, pc);
BeanMetadataElement introspector = getIntrospector(element);
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
BeanDefinitionBuilder opaqueTokenProviderBuilder = BeanDefinitionBuilder
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
if (StringUtils.hasText(authenticationConverterRef)) {
opaqueTokenProviderBuilder.addPropertyValue(AUTHENTICATION_CONVERTER,
new RuntimeBeanReference(authenticationConverterRef));
}
return opaqueTokenProviderBuilder.getBeanDefinition();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
Expand All @@ -35,6 +36,7 @@
import reactor.util.context.Context;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
Expand Down Expand Up @@ -95,6 +97,7 @@
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.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.web.access.server.BearerTokenServerAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
Expand Down Expand Up @@ -4283,6 +4286,8 @@ public final class OpaqueTokenSpec {

private Supplier<ReactiveOpaqueTokenIntrospector> introspector;

private Supplier<ReactiveOpaqueTokenAuthenticationConverter> authenticationConverter;

private OpaqueTokenSpec() {
}

Expand Down Expand Up @@ -4321,6 +4326,13 @@ public OpaqueTokenSpec introspector(ReactiveOpaqueTokenIntrospector introspector
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
* {@link ServerHttpSecurity}
Expand All @@ -4331,7 +4343,11 @@ public OAuth2ResourceServerSpec and() {
}

protected ReactiveAuthenticationManager getAuthenticationManager() {
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
final OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
getIntrospector());
Optional.ofNullable(getAuthenticationConverter())
.ifPresent(authenticationManager::setAuthenticationConverter);
return authenticationManager;
}

protected ReactiveOpaqueTokenIntrospector getIntrospector() {
Expand All @@ -4341,6 +4357,18 @@ protected ReactiveOpaqueTokenIntrospector getIntrospector() {
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) {
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

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

/**
Expand All @@ -30,21 +31,29 @@ import org.springframework.security.oauth2.server.resource.introspection.Reactiv
class ServerOpaqueTokenDsl {
private var _introspectionUri: String? = null
private var _introspector: ReactiveOpaqueTokenIntrospector? = null
private var _authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter? = null
private var clientCredentials: Pair<String, String>? = null

var introspectionUri: String?
get() = _introspectionUri
set(value) {
_introspectionUri = value
_introspector = null
_authenticationConverter = null
}
var introspector: ReactiveOpaqueTokenIntrospector?
get() = _introspector
set(value) {
_introspector = value
_authenticationConverter = null
_introspectionUri = null
clientCredentials = null
}
var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter?
get() = _authenticationConverter
set(value) {
_authenticationConverter = value
}

/**
* Configures the credentials for Introspection endpoint.
Expand All @@ -55,13 +64,15 @@ class ServerOpaqueTokenDsl {
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
clientCredentials = Pair(clientId, clientSecret)
_introspector = null
_authenticationConverter = null
}

internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
return { opaqueToken ->
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
introspector?.also { opaqueToken.introspector(introspector) }
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
import org.springframework.security.core.Authentication
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector

/**
Expand All @@ -37,6 +38,7 @@ class OpaqueTokenDsl {
private var _introspectionUri: String? = null
private var _introspector: OpaqueTokenIntrospector? = null
private var clientCredentials: Pair<String, String>? = null
private var _authenticationConverter: OpaqueTokenAuthenticationConverter? = null

var authenticationManager: AuthenticationManager? = null

Expand All @@ -54,6 +56,11 @@ class OpaqueTokenDsl {
clientCredentials = null
}

var authenticationConverter: OpaqueTokenAuthenticationConverter?
get() = _authenticationConverter
set(value) {
_authenticationConverter = value
}

/**
* Configures the credentials for Introspection endpoint.
Expand All @@ -70,6 +77,7 @@ class OpaqueTokenDsl {
return { opaqueToken ->
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
introspector?.also { opaqueToken.introspector(introspector) }
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
authenticationManager?.also { opaqueToken.authenticationManager(authenticationManager) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,9 @@ opaque-token.attlist &=
opaque-token.attlist &=
## Reference to an OpaqueTokenIntrospector
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 =
## 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>.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2060,6 +2060,13 @@
</xs:documentation>
</xs:annotation>
</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:element name="attribute-exchange">
Expand Down
Loading

0 comments on commit d2b2d66

Please sign in to comment.