Skip to content

Commit d2b2d66

Browse files
committed
gh-11661 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.
1 parent 5ae492b commit d2b2d66

File tree

16 files changed

+385
-48
lines changed

16 files changed

+385
-48
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
import java.util.HashMap;
2222
import java.util.LinkedHashMap;
2323
import java.util.Map;
24+
import java.util.Optional;
2425
import java.util.function.Supplier;
2526

2627
import javax.servlet.http.HttpServletRequest;
2728

29+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2830
import org.springframework.context.ApplicationContext;
2931
import org.springframework.core.convert.converter.Converter;
3032
import org.springframework.http.MediaType;
@@ -46,6 +48,7 @@
4648
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
4749
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
4850
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
51+
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
4952
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
5053
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
5154
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
@@ -107,8 +110,8 @@
107110
* </ul>
108111
*
109112
* <p>
110-
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its
111-
* authentication configuration
113+
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint with its
114+
* client credentials and an OpaqueTokenAuthenticationConverter
112115
* </p>
113116
*
114117
* <h2>Security Filters</h2>
@@ -138,6 +141,7 @@
138141
*
139142
* @author Josh Cummings
140143
* @author Evgeniy Cheban
144+
* @author Jerome Wacongne &lt;[email protected]&gt;
141145
* @since 5.1
142146
* @see BearerTokenAuthenticationFilter
143147
* @see JwtAuthenticationProvider
@@ -456,6 +460,8 @@ public class OpaqueTokenConfigurer {
456460

457461
private Supplier<OpaqueTokenIntrospector> introspector;
458462

463+
private Supplier<OpaqueTokenAuthenticationConverter> authenticationConverter;
464+
459465
OpaqueTokenConfigurer(ApplicationContext context) {
460466
this.context = context;
461467
}
@@ -490,19 +496,41 @@ public OpaqueTokenConfigurer introspector(OpaqueTokenIntrospector introspector)
490496
return this;
491497
}
492498

499+
public OpaqueTokenConfigurer authenticationConverter(
500+
OpaqueTokenAuthenticationConverter authenticationConverter) {
501+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
502+
this.authenticationConverter = () -> authenticationConverter;
503+
return this;
504+
}
505+
493506
OpaqueTokenIntrospector getIntrospector() {
494507
if (this.introspector != null) {
495508
return this.introspector.get();
496509
}
497510
return this.context.getBean(OpaqueTokenIntrospector.class);
498511
}
499512

513+
Optional<OpaqueTokenAuthenticationConverter> getAuthenticationConverter() {
514+
if (this.authenticationConverter != null) {
515+
return Optional.of(this.authenticationConverter.get());
516+
}
517+
try {
518+
return Optional.of(this.context.getBean(OpaqueTokenAuthenticationConverter.class));
519+
}
520+
catch (NoSuchBeanDefinitionException nsbde) {
521+
return Optional.empty();
522+
}
523+
}
524+
500525
AuthenticationProvider getAuthenticationProvider() {
501526
if (this.authenticationManager != null) {
502527
return null;
503528
}
504529
OpaqueTokenIntrospector introspector = getIntrospector();
505-
return new OpaqueTokenAuthenticationProvider(introspector);
530+
final OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
531+
introspector);
532+
getAuthenticationConverter().ifPresent(opaqueTokenAuthenticationProvider::setAuthenticationConverter);
533+
return opaqueTokenAuthenticationProvider;
506534
}
507535

508536
AuthenticationManager getAuthenticationManager(H http) {

config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,16 +251,24 @@ static final class OpaqueTokenBeanDefinitionParser implements BeanDefinitionPars
251251

252252
static final String CLIENT_SECRET = "client-secret";
253253

254+
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
255+
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
256+
254257
OpaqueTokenBeanDefinitionParser() {
255258
}
256259

257260
@Override
258261
public BeanDefinition parse(Element element, ParserContext pc) {
259262
validateConfiguration(element, pc);
260263
BeanMetadataElement introspector = getIntrospector(element);
264+
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
261265
BeanDefinitionBuilder opaqueTokenProviderBuilder = BeanDefinitionBuilder
262266
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
263267
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
268+
if (StringUtils.hasText(authenticationConverterRef)) {
269+
opaqueTokenProviderBuilder.addPropertyValue(AUTHENTICATION_CONVERTER,
270+
new RuntimeBeanReference(authenticationConverterRef));
271+
}
264272
return opaqueTokenProviderBuilder.getBeanDefinition();
265273
}
266274

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Optional;
3031
import java.util.UUID;
3132
import java.util.function.Function;
3233
import java.util.function.Supplier;
@@ -35,6 +36,7 @@
3536
import reactor.util.context.Context;
3637

3738
import org.springframework.beans.BeansException;
39+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3840
import org.springframework.context.ApplicationContext;
3941
import org.springframework.core.Ordered;
4042
import org.springframework.core.ResolvableType;
@@ -95,6 +97,7 @@
9597
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
9698
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
9799
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
100+
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
98101
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
99102
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
100103
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
@@ -4283,6 +4286,8 @@ public final class OpaqueTokenSpec {
42834286

42844287
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
42854288

4289+
private Supplier<ReactiveOpaqueTokenAuthenticationConverter> authenticationConverter;
4290+
42864291
private OpaqueTokenSpec() {
42874292
}
42884293

@@ -4321,6 +4326,13 @@ public OpaqueTokenSpec introspector(ReactiveOpaqueTokenIntrospector introspector
43214326
return this;
43224327
}
43234328

4329+
public OpaqueTokenSpec authenticationConverter(
4330+
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
4331+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
4332+
this.authenticationConverter = () -> authenticationConverter;
4333+
return this;
4334+
}
4335+
43244336
/**
43254337
* Allows method chaining to continue configuring the
43264338
* {@link ServerHttpSecurity}
@@ -4331,7 +4343,11 @@ public OAuth2ResourceServerSpec and() {
43314343
}
43324344

43334345
protected ReactiveAuthenticationManager getAuthenticationManager() {
4334-
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
4346+
final OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
4347+
getIntrospector());
4348+
Optional.ofNullable(getAuthenticationConverter())
4349+
.ifPresent(authenticationManager::setAuthenticationConverter);
4350+
return authenticationManager;
43354351
}
43364352

43374353
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
@@ -4341,6 +4357,18 @@ protected ReactiveOpaqueTokenIntrospector getIntrospector() {
43414357
return getBean(ReactiveOpaqueTokenIntrospector.class);
43424358
}
43434359

4360+
protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() {
4361+
if (this.authenticationConverter != null) {
4362+
return this.authenticationConverter.get();
4363+
}
4364+
try {
4365+
return getBean(ReactiveOpaqueTokenAuthenticationConverter.class);
4366+
}
4367+
catch (NoSuchBeanDefinitionException nsbde) {
4368+
return null;
4369+
}
4370+
}
4371+
43444372
protected void configure(ServerHttpSecurity http) {
43454373
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
43464374
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);

config/src/main/kotlin/org/springframework/security/config/web/server/ServerOpaqueTokenDsl.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.security.config.web.server
1818

19+
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter
1920
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
2021

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

3537
var introspectionUri: String?
3638
get() = _introspectionUri
3739
set(value) {
3840
_introspectionUri = value
3941
_introspector = null
42+
_authenticationConverter = null
4043
}
4144
var introspector: ReactiveOpaqueTokenIntrospector?
4245
get() = _introspector
4346
set(value) {
4447
_introspector = value
48+
_authenticationConverter = null
4549
_introspectionUri = null
4650
clientCredentials = null
4751
}
52+
var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter?
53+
get() = _authenticationConverter
54+
set(value) {
55+
_authenticationConverter = value
56+
}
4857

4958
/**
5059
* Configures the credentials for Introspection endpoint.
@@ -55,13 +64,15 @@ class ServerOpaqueTokenDsl {
5564
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
5665
clientCredentials = Pair(clientId, clientSecret)
5766
_introspector = null
67+
_authenticationConverter = null
5868
}
5969

6070
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
6171
return { opaqueToken ->
6272
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
6373
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
6474
introspector?.also { opaqueToken.introspector(introspector) }
75+
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
6576
}
6677
}
6778
}

config/src/main/kotlin/org/springframework/security/config/web/servlet/oauth2/resourceserver/OpaqueTokenDsl.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import org.springframework.security.authentication.AuthenticationManager
2020
import org.springframework.security.config.annotation.web.builders.HttpSecurity
2121
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
2222
import org.springframework.security.core.Authentication
23+
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter
2324
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
2425

2526
/**
@@ -37,6 +38,7 @@ class OpaqueTokenDsl {
3738
private var _introspectionUri: String? = null
3839
private var _introspector: OpaqueTokenIntrospector? = null
3940
private var clientCredentials: Pair<String, String>? = null
41+
private var _authenticationConverter: OpaqueTokenAuthenticationConverter? = null
4042

4143
var authenticationManager: AuthenticationManager? = null
4244

@@ -54,6 +56,11 @@ class OpaqueTokenDsl {
5456
clientCredentials = null
5557
}
5658

59+
var authenticationConverter: OpaqueTokenAuthenticationConverter?
60+
get() = _authenticationConverter
61+
set(value) {
62+
_authenticationConverter = value
63+
}
5764

5865
/**
5966
* Configures the credentials for Introspection endpoint.
@@ -70,6 +77,7 @@ class OpaqueTokenDsl {
7077
return { opaqueToken ->
7178
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
7279
introspector?.also { opaqueToken.introspector(introspector) }
80+
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
7381
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
7482
authenticationManager?.also { opaqueToken.authenticationManager(authenticationManager) }
7583
}

config/src/main/resources/org/springframework/security/config/spring-security-5.8.rnc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,9 @@ opaque-token.attlist &=
667667
opaque-token.attlist &=
668668
## Reference to an OpaqueTokenIntrospector
669669
attribute introspector-ref {xsd:token}?
670+
opaque-token.attlist &=
671+
## Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful introspection result into an Authentication.
672+
attribute authentication-converter-ref {xsd:token}?
670673

671674
openid-login =
672675
## 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>.

config/src/main/resources/org/springframework/security/config/spring-security-5.8.xsd

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,6 +2060,13 @@
20602060
</xs:documentation>
20612061
</xs:annotation>
20622062
</xs:attribute>
2063+
<xs:attribute name="authentication-converter-ref" type="xs:token">
2064+
<xs:annotation>
2065+
<xs:documentation>Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful
2066+
introspection result into an Authentication.
2067+
</xs:documentation>
2068+
</xs:annotation>
2069+
</xs:attribute>
20632070
</xs:attributeGroup>
20642071

20652072
<xs:element name="attribute-exchange">

0 commit comments

Comments
 (0)