Skip to content

Configurable authentication converter for resource-servers with token introspection #11665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;[email protected]&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