Polish gh-11665

This commit is contained in:
Steve Riesenberg 2022-09-06 20:28:10 -05:00
parent 1efb63387f
commit 355ef21117
No known key found for this signature in database
GPG Key ID: 5F311AB48A55D521
14 changed files with 258 additions and 88 deletions

View File

@ -21,12 +21,10 @@ 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;
@ -460,7 +458,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private Supplier<OpaqueTokenIntrospector> introspector; private Supplier<OpaqueTokenIntrospector> introspector;
private Supplier<OpaqueTokenAuthenticationConverter> authenticationConverter; private OpaqueTokenAuthenticationConverter authenticationConverter;
OpaqueTokenConfigurer(ApplicationContext context) { OpaqueTokenConfigurer(ApplicationContext context) {
this.context = context; this.context = context;
@ -499,7 +497,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
public OpaqueTokenConfigurer authenticationConverter( public OpaqueTokenConfigurer authenticationConverter(
OpaqueTokenAuthenticationConverter authenticationConverter) { OpaqueTokenAuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = () -> authenticationConverter; this.authenticationConverter = authenticationConverter;
return this; return this;
} }
@ -510,16 +508,14 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
return this.context.getBean(OpaqueTokenIntrospector.class); return this.context.getBean(OpaqueTokenIntrospector.class);
} }
Optional<OpaqueTokenAuthenticationConverter> getAuthenticationConverter() { OpaqueTokenAuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter != null) { if (this.authenticationConverter != null) {
return Optional.of(this.authenticationConverter.get()); return this.authenticationConverter;
} }
try { if (this.context.getBeanNamesForType(OpaqueTokenAuthenticationConverter.class).length > 0) {
return Optional.of(this.context.getBean(OpaqueTokenAuthenticationConverter.class)); return this.context.getBean(OpaqueTokenAuthenticationConverter.class);
}
catch (NoSuchBeanDefinitionException nsbde) {
return Optional.empty();
} }
return null;
} }
AuthenticationProvider getAuthenticationProvider() { AuthenticationProvider getAuthenticationProvider() {
@ -527,9 +523,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
return null; return null;
} }
OpaqueTokenIntrospector introspector = getIntrospector(); OpaqueTokenIntrospector introspector = getIntrospector();
final OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider( OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
introspector); introspector);
getAuthenticationConverter().ifPresent(opaqueTokenAuthenticationProvider::setAuthenticationConverter); OpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
if (authenticationConverter != null) {
opaqueTokenAuthenticationProvider.setAuthenticationConverter(authenticationConverter);
}
return opaqueTokenAuthenticationProvider; return opaqueTokenAuthenticationProvider;
} }

View File

@ -252,6 +252,7 @@ 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_REF = "authentication-converter-ref";
static final String AUTHENTICATION_CONVERTER = "authenticationConverter"; static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
OpaqueTokenBeanDefinitionParser() { OpaqueTokenBeanDefinitionParser() {
@ -266,8 +267,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class); .rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
opaqueTokenProviderBuilder.addConstructorArgValue(introspector); opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
if (StringUtils.hasText(authenticationConverterRef)) { if (StringUtils.hasText(authenticationConverterRef)) {
opaqueTokenProviderBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, opaqueTokenProviderBuilder.addPropertyReference(AUTHENTICATION_CONVERTER, authenticationConverterRef);
new RuntimeBeanReference(authenticationConverterRef));
} }
return opaqueTokenProviderBuilder.getBeanDefinition(); return opaqueTokenProviderBuilder.getBeanDefinition();
} }

View File

@ -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.
@ -27,7 +27,6 @@ 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;
@ -36,7 +35,6 @@ 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;
@ -4286,7 +4284,7 @@ public class ServerHttpSecurity {
private Supplier<ReactiveOpaqueTokenIntrospector> introspector; private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
private Supplier<ReactiveOpaqueTokenAuthenticationConverter> authenticationConverter; private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter;
private OpaqueTokenSpec() { private OpaqueTokenSpec() {
} }
@ -4329,7 +4327,7 @@ public class ServerHttpSecurity {
public OpaqueTokenSpec authenticationConverter( public OpaqueTokenSpec authenticationConverter(
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) { ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = () -> authenticationConverter; this.authenticationConverter = authenticationConverter;
return this; return this;
} }
@ -4343,10 +4341,12 @@ public class ServerHttpSecurity {
} }
protected ReactiveAuthenticationManager getAuthenticationManager() { protected ReactiveAuthenticationManager getAuthenticationManager() {
final OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager( OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
getIntrospector()); getIntrospector());
Optional.ofNullable(getAuthenticationConverter()) ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
.ifPresent(authenticationManager::setAuthenticationConverter); if (authenticationConverter != null) {
authenticationManager.setAuthenticationConverter(authenticationConverter);
}
return authenticationManager; return authenticationManager;
} }
@ -4359,14 +4359,9 @@ public class ServerHttpSecurity {
protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() { protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter != null) { if (this.authenticationConverter != null) {
return this.authenticationConverter.get(); return this.authenticationConverter;
}
try {
return getBean(ReactiveOpaqueTokenAuthenticationConverter.class);
}
catch (NoSuchBeanDefinitionException nsbde) {
return null;
} }
return getBeanOrNull(ReactiveOpaqueTokenAuthenticationConverter.class);
} }
protected void configure(ServerHttpSecurity http) { protected void configure(ServerHttpSecurity http) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2020 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.
@ -31,7 +31,6 @@ 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?
@ -39,21 +38,15 @@ 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? var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter? = null
get() = _authenticationConverter
set(value) {
_authenticationConverter = value
}
/** /**
* Configures the credentials for Introspection endpoint. * Configures the credentials for Introspection endpoint.
@ -64,7 +57,6 @@ 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 {

View File

@ -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.
@ -38,7 +38,6 @@ 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
@ -56,11 +55,7 @@ class OpaqueTokenDsl {
clientCredentials = null clientCredentials = null
} }
var authenticationConverter: OpaqueTokenAuthenticationConverter? var authenticationConverter: OpaqueTokenAuthenticationConverter? = null
get() = _authenticationConverter
set(value) {
_authenticationConverter = value
}
/** /**
* Configures the credentials for Introspection endpoint. * Configures the credentials for Introspection endpoint.

View File

@ -82,6 +82,7 @@ import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationManagerResolver;
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.TestingAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@ -103,6 +104,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations; import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
@ -121,6 +123,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAut
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
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.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
@ -1387,6 +1390,22 @@ public class OAuth2ResourceServerConfigurerTests {
.isThrownBy(jwtConfigurer::getJwtAuthenticationConverter); .isThrownBy(jwtConfigurer::getJwtAuthenticationConverter);
} }
@Test
public void getWhenCustomAuthenticationConverterThenConverts() throws Exception {
this.spring.register(RestOperationsConfig.class, OpaqueTokenAuthenticationConverterConfig.class,
BasicController.class).autowire();
OpaqueTokenAuthenticationConverter authenticationConverter = this.spring.getContext()
.getBean(OpaqueTokenAuthenticationConverter.class);
given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class)))
.willReturn(new TestingAuthenticationToken("jdoe", null, Collections.emptyList()));
mockRestOperations(json("Active"));
// @formatter:off
this.mvc.perform(get("/authenticated").with(bearerToken("token")))
.andExpect(status().isOk())
.andExpect(content().string("jdoe"));
// @formatter:on
}
private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) { private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) {
context.registerBean(name, clazz, () -> mock(clazz)); context.registerBean(name, clazz, () -> mock(clazz));
} }
@ -2441,6 +2460,30 @@ public class OAuth2ResourceServerConfigurerTests {
} }
@EnableWebSecurity
static class OpaqueTokenAuthenticationConverterConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.antMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read")
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.opaqueToken()
.authenticationConverter(authenticationConverter());
// @formatter:on
}
@Bean
OpaqueTokenAuthenticationConverter authenticationConverter() {
return mock(OpaqueTokenAuthenticationConverter.class);
}
}
@Configuration @Configuration
static class JwtDecoderConfig { static class JwtDecoderConfig {

View File

@ -24,6 +24,7 @@ import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec; import java.security.spec.RSAPublicKeySpec;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -52,11 +53,13 @@ import org.springframework.http.MediaType;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
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.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
@ -66,6 +69,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenAuthentica
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.ReactiveJwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
@ -567,6 +571,25 @@ public class OAuth2ResourceServerSpecTests {
.withMessageContaining("authenticationManagerResolver"); .withMessageContaining("authenticationManagerResolver");
} }
@Test
public void getWhenCustomAuthenticationConverterThenConverts() {
this.spring.register(ReactiveOpaqueTokenAuthenticationConverterConfig.class, RootController.class).autowire();
this.spring.getContext().getBean(MockWebServer.class)
.setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active));
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = this.spring.getContext()
.getBean(ReactiveOpaqueTokenAuthenticationConverter.class);
given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class)))
.willReturn(Mono.just(new TestingAuthenticationToken("jdoe", null, Collections.emptyList())));
// @formatter:off
this.client.get()
.headers((headers) -> headers
.setBearerAuth(this.messageReadToken)
)
.exchange()
.expectStatus().isOk();
// @formatter:on
}
private static Dispatcher requiresAuth(String username, String password, String response) { private static Dispatcher requiresAuth(String username, String password, String response) {
return new Dispatcher() { return new Dispatcher() {
@Override @Override
@ -1037,6 +1060,43 @@ public class OAuth2ResourceServerSpecTests {
} }
@EnableWebFlux
@EnableWebFluxSecurity
static class ReactiveOpaqueTokenAuthenticationConverterConfig {
private MockWebServer mockWebServer = new MockWebServer();
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
String introspectionUri = mockWebServer().url("/introspect").toString();
// @formatter:off
http
.oauth2ResourceServer()
.opaqueToken()
.introspectionUri(introspectionUri)
.introspectionClientCredentials("client", "secret")
.authenticationConverter(authenticationConverter());
// @formatter:on
return http.build();
}
@Bean
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter() {
return mock(ReactiveOpaqueTokenAuthenticationConverter.class);
}
@Bean
MockWebServer mockWebServer() {
return this.mockWebServer;
}
@PreDestroy
void shutdown() throws IOException {
this.mockWebServer.shutdown();
}
}
@RestController @RestController
static class RootController { static class RootController {

View File

@ -313,7 +313,8 @@ The filter chain is specified like so:
<http> <http>
<intercept-uri pattern="/**" access="authenticated"/> <intercept-uri pattern="/**" access="authenticated"/>
<oauth2-resource-server> <oauth2-resource-server>
<opaque-token introspector-ref="opaqueTokenIntrospector"/> <opaque-token introspector-ref="opaqueTokenIntrospector"
authentication-converter-ref="opaqueTokenAuthenticationConverter"/>
</oauth2-resource-server> </oauth2-resource-server>
</http> </http>
---- ----
@ -335,6 +336,18 @@ And the <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntr
---- ----
==== ====
And the `OpaqueTokenAuthenticationConverter` like so:
.Opaque Token Authentication Converter
====
.Xml
[source,xml,role="primary"]
----
<bean id="opaqueTokenAuthenticationConverter"
class="com.example.CustomOpaqueTokenAuthenticationConverter"/>
----
====
[[oauth2resourceserver-opaque-introspectionuri-dsl]] [[oauth2resourceserver-opaque-introspectionuri-dsl]]
=== Using `introspectionUri()` === Using `introspectionUri()`

View File

@ -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.
@ -17,6 +17,7 @@
package org.springframework.security.oauth2.server.resource.authentication; package org.springframework.security.oauth2.server.resource.authentication;
import java.time.Instant; import java.time.Instant;
import java.util.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;
@ -24,7 +25,6 @@ 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;
@ -50,18 +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>
* This {@link ReactiveAuthenticationManager} is responsible for introspecting and * Scopes are translated into {@link GrantedAuthority}s according to the following
* verifying an opaque access token, returning its attributes set as part of the * algorithm:
* {@link Authentication} statement. * <ol>
* <li>If there is a "scope" attribute, then convert to a {@link Collection} of
* {@link String}s.
* <li>Take the resulting {@link Collection} and prepend the "SCOPE_" keyword to each
* element, adding as {@link GrantedAuthority}s.
* </ol>
* <p> * <p>
* An {@link OpaqueTokenIntrospector} is responsible for retrieving token attributes from
* an authorization server.
* <p> * <p>
* {@link org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector} * An {@link OpaqueTokenAuthenticationConverter} is responsible for turning a successful
* is responsible for retrieving token attributes from authorization-server. * introspection result into an {@link Authentication} instance (which may include mapping
* </p> * {@link GrantedAuthority}s from token attributes or retrieving from another source).
* <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 &lt;ch4mp@c4-soft.com&gt; * @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt;
@ -74,7 +77,7 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
private final OpaqueTokenIntrospector introspector; private final OpaqueTokenIntrospector introspector;
private OpaqueTokenAuthenticationConverter authenticationConverter; private OpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenAuthenticationProvider::convert;
/** /**
* Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters * Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters
@ -83,20 +86,16 @@ 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> and then delegates {@link Authentication} instantiation to * Token</a> and then delegates {@link Authentication} instantiation to
* {@link OpaqueTokenAuthenticationConverter}. * {@link OpaqueTokenAuthenticationConverter}.
* </p>
* <p> * <p>
* If created Authentication is instance of {@link AbstractAuthenticationToken} and * If created Authentication is instance of {@link AbstractAuthenticationToken} and
* details are null, then introspection result details are used. * 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
@ -142,9 +141,9 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
/** /**
* Default {@link OpaqueTokenAuthenticationConverter}. * Default {@link OpaqueTokenAuthenticationConverter}.
* @param introspectedToken the bearer sring that was successfuly introspected * @param introspectedToken the bearer string that was successfully introspected
* @param authenticatedPrincipal the successful introspection output * @param authenticatedPrincipal the successful introspection output
* @returna {@link BearerTokenAuthentication} * @return a {@link BearerTokenAuthentication}
*/ */
static BearerTokenAuthentication convert(String introspectedToken, static BearerTokenAuthentication convert(String introspectedToken,
OAuth2AuthenticatedPrincipal authenticatedPrincipal) { OAuth2AuthenticatedPrincipal authenticatedPrincipal) {

View File

@ -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.
@ -36,7 +36,7 @@ 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.
@ -45,14 +45,13 @@ 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>
* A {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token
* attributes from an authorization server.
* <p> * <p>
* {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token attributes * A {@link ReactiveOpaqueTokenAuthenticationConverter} is responsible for turning a
* from authorization-server. * successful introspection result into an {@link Authentication} instance (which may
* </p> * include mapping {@link GrantedAuthority}s from token attributes or retrieving from
* <p> * another source).
* authenticationConverter is responsible for turning successful introspection into
* {@link Authentication} (which includes {@link GrantedAuthority}s mapping from token
* attributes or retrieving from another source)
* *
* @author Josh Cummings * @author Josh Cummings
* @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt; * @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt;
@ -63,7 +62,7 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
private final ReactiveOpaqueTokenIntrospector introspector; private final ReactiveOpaqueTokenIntrospector introspector;
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter; private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenReactiveAuthenticationManager::convert;
/** /**
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided * Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
@ -73,20 +72,16 @@ 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 * 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> and then delegates {@link Authentication} instantiation to * Token</a> and then delegates {@link Authentication} instantiation to
* {@link OpaqueTokenAuthenticationConverter}. * {@link ReactiveOpaqueTokenAuthenticationConverter}.
* </p>
* <p> * <p>
* If created Authentication is instance of {@link AbstractAuthenticationToken} and * If created Authentication is instance of {@link AbstractAuthenticationToken} and
* details are null, then introspection result details are used. * 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
*/ */
@ -117,10 +112,10 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
} }
/** /**
* Default reactive {@link OpaqueTokenAuthenticationConverter}. * Default {@link ReactiveOpaqueTokenAuthenticationConverter}.
* @param introspectedToken the bearer sring that was successfuly introspected * @param introspectedToken the bearer string that was successfully introspected
* @param authenticatedPrincipal the successful introspection output * @param authenticatedPrincipal the successful introspection output
* @returna an async wrapper of default {@link OpaqueTokenAuthenticationConverter} * @return an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
* result * result
*/ */
static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) { static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {

View File

@ -20,7 +20,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
/** /**
* Turn successful introspection result into an Authentication instance * Convert a successful introspection result into an authentication result.
* *
* @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt; * @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt;
* @since 5.8 * @since 5.8
@ -28,6 +28,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
@FunctionalInterface @FunctionalInterface
public interface OpaqueTokenAuthenticationConverter { public interface OpaqueTokenAuthenticationConverter {
/**
* Converts a successful introspection result into an authentication result.
* @param introspectedToken the bearer token used to perform token introspection
* @param authenticatedPrincipal the result of token introspection
* @return an {@link Authentication} instance
*/
Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal); Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal);
} }

View File

@ -22,7 +22,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
/** /**
* Turn successful introspection result into an Authentication instance * Convert a successful introspection result into an authentication result.
* *
* @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt; * @author Jerome Wacongne &lt;ch4mp@c4-soft.com&gt;
* @since 5.8 * @since 5.8
@ -30,6 +30,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
@FunctionalInterface @FunctionalInterface
public interface ReactiveOpaqueTokenAuthenticationConverter { public interface ReactiveOpaqueTokenAuthenticationConverter {
/**
* Converts a successful introspection result into an authentication result.
* @param introspectedToken the bearer token used to perform token introspection
* @param authenticatedPrincipal the result of token introspection
* @return an {@link Authentication} instance
*/
Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal); Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal);
} }

View File

@ -25,6 +25,7 @@ import java.util.Map;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
@ -32,6 +33,7 @@ import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipal
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
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 static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -40,6 +42,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Tests for {@link OpaqueTokenAuthenticationProvider} * Tests for {@link OpaqueTokenAuthenticationProvider}
@ -114,4 +118,33 @@ public class OpaqueTokenAuthenticationProviderTests {
// @formatter:on // @formatter:on
} }
@Test
public void setAuthenticationConverterWhenNullThenThrowsIllegalArgumentException() {
OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class);
OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> provider.setAuthenticationConverter(null))
.withMessage("authenticationConverter cannot be null");
// @formatter:on
}
@Test
public void authenticateWhenCustomAuthenticationConverterThenUses() {
OpaqueTokenIntrospector introspector = mock(OpaqueTokenIntrospector.class);
OAuth2AuthenticatedPrincipal principal = TestOAuth2AuthenticatedPrincipals.active();
given(introspector.introspect(any())).willReturn(principal);
OpaqueTokenAuthenticationProvider provider = new OpaqueTokenAuthenticationProvider(introspector);
OpaqueTokenAuthenticationConverter authenticationConverter = mock(OpaqueTokenAuthenticationConverter.class);
given(authenticationConverter.convert(any(), any(OAuth2AuthenticatedPrincipal.class)))
.willReturn(new TestingAuthenticationToken(principal, null, Collections.emptyList()));
provider.setAuthenticationConverter(authenticationConverter);
Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token"));
assertThat(result).isNotNull();
verify(introspector).introspect("token");
verify(authenticationConverter).convert("token", principal);
verifyNoMoreInteractions(introspector, authenticationConverter);
}
} }

View File

@ -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.
@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
@ -33,6 +34,7 @@ import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipal
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal; import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
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.ReactiveOpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector; import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -41,6 +43,8 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/** /**
* Tests for {@link OpaqueTokenReactiveAuthenticationManager} * Tests for {@link OpaqueTokenReactiveAuthenticationManager}
@ -112,4 +116,34 @@ public class OpaqueTokenReactiveAuthenticationManagerTests {
// @formatter:on // @formatter:on
} }
@Test
public void setAuthenticationConverterWhenNullThenThrowsIllegalArgumentException() {
ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class);
OpaqueTokenReactiveAuthenticationManager provider = new OpaqueTokenReactiveAuthenticationManager(introspector);
// @formatter:off
assertThatIllegalArgumentException()
.isThrownBy(() -> provider.setAuthenticationConverter(null))
.withMessage("authenticationConverter cannot be null");
// @formatter:on
}
@Test
public void authenticateWhenCustomAuthenticationConverterThenUses() {
ReactiveOpaqueTokenIntrospector introspector = mock(ReactiveOpaqueTokenIntrospector.class);
OAuth2AuthenticatedPrincipal principal = TestOAuth2AuthenticatedPrincipals.active();
given(introspector.introspect(any())).willReturn(Mono.just(principal));
OpaqueTokenReactiveAuthenticationManager provider = new OpaqueTokenReactiveAuthenticationManager(introspector);
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = mock(
ReactiveOpaqueTokenAuthenticationConverter.class);
given(authenticationConverter.convert(any(), any(OAuth2AuthenticatedPrincipal.class)))
.willReturn(Mono.just(new TestingAuthenticationToken(principal, null, Collections.emptyList())));
provider.setAuthenticationConverter(authenticationConverter);
Authentication result = provider.authenticate(new BearerTokenAuthenticationToken("token")).block();
assertThat(result).isNotNull();
verify(introspector).introspect("token");
verify(authenticationConverter).convert("token", principal);
verifyNoMoreInteractions(introspector, authenticationConverter);
}
} }