mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-29 16:22:12 +00:00
Polish gh-11665
This commit is contained in:
parent
1efb63387f
commit
355ef21117
@ -21,12 +21,10 @@ import java.util.Collections;
|
||||
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;
|
||||
@ -460,7 +458,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
|
||||
private Supplier<OpaqueTokenIntrospector> introspector;
|
||||
|
||||
private Supplier<OpaqueTokenAuthenticationConverter> authenticationConverter;
|
||||
private OpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
|
||||
OpaqueTokenConfigurer(ApplicationContext context) {
|
||||
this.context = context;
|
||||
@ -499,7 +497,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
public OpaqueTokenConfigurer authenticationConverter(
|
||||
OpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = () -> authenticationConverter;
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -510,16 +508,14 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
return this.context.getBean(OpaqueTokenIntrospector.class);
|
||||
}
|
||||
|
||||
Optional<OpaqueTokenAuthenticationConverter> getAuthenticationConverter() {
|
||||
OpaqueTokenAuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter != null) {
|
||||
return Optional.of(this.authenticationConverter.get());
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
try {
|
||||
return Optional.of(this.context.getBean(OpaqueTokenAuthenticationConverter.class));
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException nsbde) {
|
||||
return Optional.empty();
|
||||
if (this.context.getBeanNamesForType(OpaqueTokenAuthenticationConverter.class).length > 0) {
|
||||
return this.context.getBean(OpaqueTokenAuthenticationConverter.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
AuthenticationProvider getAuthenticationProvider() {
|
||||
@ -527,9 +523,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
||||
return null;
|
||||
}
|
||||
OpaqueTokenIntrospector introspector = getIntrospector();
|
||||
final OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
|
||||
OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
|
||||
introspector);
|
||||
getAuthenticationConverter().ifPresent(opaqueTokenAuthenticationProvider::setAuthenticationConverter);
|
||||
OpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
|
||||
if (authenticationConverter != null) {
|
||||
opaqueTokenAuthenticationProvider.setAuthenticationConverter(authenticationConverter);
|
||||
}
|
||||
return opaqueTokenAuthenticationProvider;
|
||||
}
|
||||
|
||||
|
@ -252,6 +252,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
||||
static final String CLIENT_SECRET = "client-secret";
|
||||
|
||||
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
|
||||
|
||||
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
|
||||
|
||||
OpaqueTokenBeanDefinitionParser() {
|
||||
@ -266,8 +267,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
||||
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
|
||||
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
|
||||
if (StringUtils.hasText(authenticationConverterRef)) {
|
||||
opaqueTokenProviderBuilder.addPropertyValue(AUTHENTICATION_CONVERTER,
|
||||
new RuntimeBeanReference(authenticationConverterRef));
|
||||
opaqueTokenProviderBuilder.addPropertyReference(AUTHENTICATION_CONVERTER, authenticationConverterRef);
|
||||
}
|
||||
return opaqueTokenProviderBuilder.getBeanDefinition();
|
||||
}
|
||||
|
@ -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");
|
||||
* 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.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@ -36,7 +35,6 @@ import reactor.core.publisher.Mono;
|
||||
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;
|
||||
@ -4286,7 +4284,7 @@ public class ServerHttpSecurity {
|
||||
|
||||
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
|
||||
|
||||
private Supplier<ReactiveOpaqueTokenAuthenticationConverter> authenticationConverter;
|
||||
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
|
||||
private OpaqueTokenSpec() {
|
||||
}
|
||||
@ -4329,7 +4327,7 @@ public class ServerHttpSecurity {
|
||||
public OpaqueTokenSpec authenticationConverter(
|
||||
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = () -> authenticationConverter;
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -4343,10 +4341,12 @@ public class ServerHttpSecurity {
|
||||
}
|
||||
|
||||
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
||||
final OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
|
||||
OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
|
||||
getIntrospector());
|
||||
Optional.ofNullable(getAuthenticationConverter())
|
||||
.ifPresent(authenticationManager::setAuthenticationConverter);
|
||||
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
|
||||
if (authenticationConverter != null) {
|
||||
authenticationManager.setAuthenticationConverter(authenticationConverter);
|
||||
}
|
||||
return authenticationManager;
|
||||
}
|
||||
|
||||
@ -4359,14 +4359,9 @@ public class ServerHttpSecurity {
|
||||
|
||||
protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter != null) {
|
||||
return this.authenticationConverter.get();
|
||||
}
|
||||
try {
|
||||
return getBean(ReactiveOpaqueTokenAuthenticationConverter.class);
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException nsbde) {
|
||||
return null;
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
return getBeanOrNull(ReactiveOpaqueTokenAuthenticationConverter.class);
|
||||
}
|
||||
|
||||
protected void configure(ServerHttpSecurity http) {
|
||||
|
@ -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");
|
||||
* 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 {
|
||||
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?
|
||||
@ -39,21 +38,15 @@ class ServerOpaqueTokenDsl {
|
||||
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
|
||||
}
|
||||
var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter? = null
|
||||
|
||||
/**
|
||||
* Configures the credentials for Introspection endpoint.
|
||||
@ -64,7 +57,6 @@ class ServerOpaqueTokenDsl {
|
||||
fun introspectionClientCredentials(clientId: String, clientSecret: String) {
|
||||
clientCredentials = Pair(clientId, clientSecret)
|
||||
_introspector = null
|
||||
_authenticationConverter = null
|
||||
}
|
||||
|
||||
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit {
|
||||
|
@ -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");
|
||||
* 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 _introspector: OpaqueTokenIntrospector? = null
|
||||
private var clientCredentials: Pair<String, String>? = null
|
||||
private var _authenticationConverter: OpaqueTokenAuthenticationConverter? = null
|
||||
|
||||
var authenticationManager: AuthenticationManager? = null
|
||||
|
||||
@ -56,11 +55,7 @@ class OpaqueTokenDsl {
|
||||
clientCredentials = null
|
||||
}
|
||||
|
||||
var authenticationConverter: OpaqueTokenAuthenticationConverter?
|
||||
get() = _authenticationConverter
|
||||
set(value) {
|
||||
_authenticationConverter = value
|
||||
}
|
||||
var authenticationConverter: OpaqueTokenAuthenticationConverter? = null
|
||||
|
||||
/**
|
||||
* Configures the credentials for Introspection endpoint.
|
||||
|
@ -82,6 +82,7 @@ import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
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.SecurityContextChangedListenerConfig;
|
||||
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.TestClientRegistrations;
|
||||
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.OAuth2Error;
|
||||
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.JwtIssuerAuthenticationManagerResolver;
|
||||
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.web.BearerTokenAuthenticationEntryPoint;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
|
||||
@ -1387,6 +1390,22 @@ public class OAuth2ResourceServerConfigurerTests {
|
||||
.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) {
|
||||
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
|
||||
static class JwtDecoderConfig {
|
||||
|
||||
|
@ -24,6 +24,7 @@ import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.RSAPublicKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
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.ReactiveAuthenticationManager;
|
||||
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.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
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.OAuth2Error;
|
||||
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.ReactiveJwtAuthenticationConverter;
|
||||
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.authentication.HttpStatusServerEntryPoint;
|
||||
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
|
||||
@ -567,6 +571,25 @@ public class OAuth2ResourceServerSpecTests {
|
||||
.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) {
|
||||
return new Dispatcher() {
|
||||
@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
|
||||
static class RootController {
|
||||
|
||||
|
@ -313,7 +313,8 @@ The filter chain is specified like so:
|
||||
<http>
|
||||
<intercept-uri pattern="/**" access="authenticated"/>
|
||||
<oauth2-resource-server>
|
||||
<opaque-token introspector-ref="opaqueTokenIntrospector"/>
|
||||
<opaque-token introspector-ref="opaqueTokenIntrospector"
|
||||
authentication-converter-ref="opaqueTokenAuthenticationConverter"/>
|
||||
</oauth2-resource-server>
|
||||
</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]]
|
||||
=== Using `introspectionUri()`
|
||||
|
||||
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,6 +17,7 @@
|
||||
package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
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.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.ReactiveAuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
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}
|
||||
* statement.
|
||||
* <p>
|
||||
* This {@link ReactiveAuthenticationManager} is responsible for introspecting and
|
||||
* verifying an opaque access token, returning its attributes set as part of the
|
||||
* {@link Authentication} statement.
|
||||
* Scopes are translated into {@link GrantedAuthority}s according to the following
|
||||
* algorithm:
|
||||
* <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>
|
||||
* An {@link OpaqueTokenIntrospector} is responsible for retrieving token attributes from
|
||||
* an authorization server.
|
||||
* <p>
|
||||
* {@link org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector}
|
||||
* is responsible for retrieving token attributes from authorization-server.
|
||||
* </p>
|
||||
* <p>
|
||||
* authenticationConverter is responsible for turning successful introspection into
|
||||
* {@link Authentication} (which includes {@link GrantedAuthority}s mapping from token
|
||||
* attributes or retrieving from an other source)
|
||||
* An {@link OpaqueTokenAuthenticationConverter} is responsible for turning a successful
|
||||
* introspection result into an {@link Authentication} instance (which may include mapping
|
||||
* {@link GrantedAuthority}s from token attributes or retrieving from another source).
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
@ -74,7 +77,7 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
||||
|
||||
private final OpaqueTokenIntrospector introspector;
|
||||
|
||||
private OpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
private OpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenAuthenticationProvider::convert;
|
||||
|
||||
/**
|
||||
* Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters
|
||||
@ -83,20 +86,16 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
||||
public OpaqueTokenAuthenticationProvider(OpaqueTokenIntrospector introspector) {
|
||||
Assert.notNull(introspector, "introspector cannot be null");
|
||||
this.introspector = introspector;
|
||||
this.setAuthenticationConverter(OpaqueTokenAuthenticationProvider::convert);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Introspect and validate the opaque
|
||||
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
||||
* Token</a> and then delegates {@link Authentication} instantiation to
|
||||
* {@link OpaqueTokenAuthenticationConverter}.
|
||||
* </p>
|
||||
* <p>
|
||||
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||
* details are null, then introspection result details are used.
|
||||
* </p>
|
||||
* @param authentication the authentication request object.
|
||||
* @return A successful authentication
|
||||
* @throws AuthenticationException if authentication failed for some reason
|
||||
@ -142,9 +141,9 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @returna {@link BearerTokenAuthentication}
|
||||
* @return a {@link BearerTokenAuthentication}
|
||||
*/
|
||||
static BearerTokenAuthentication convert(String introspectedToken,
|
||||
OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* 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
|
||||
* <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
|
||||
* <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.
|
||||
@ -45,14 +45,13 @@ import org.springframework.util.Assert;
|
||||
* verifying an opaque access token, returning its attributes set as part of the
|
||||
* {@link Authentication} statement.
|
||||
* <p>
|
||||
* A {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token
|
||||
* attributes from an authorization server.
|
||||
* <p>
|
||||
* {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token attributes
|
||||
* from authorization-server.
|
||||
* </p>
|
||||
* <p>
|
||||
* authenticationConverter is responsible for turning successful introspection into
|
||||
* {@link Authentication} (which includes {@link GrantedAuthority}s mapping from token
|
||||
* attributes or retrieving from another source)
|
||||
* A {@link ReactiveOpaqueTokenAuthenticationConverter} is responsible for turning a
|
||||
* successful introspection result into an {@link Authentication} instance (which may
|
||||
* include mapping {@link GrantedAuthority}s from token attributes or retrieving from
|
||||
* another source).
|
||||
*
|
||||
* @author Josh Cummings
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
@ -63,7 +62,7 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
||||
|
||||
private final ReactiveOpaqueTokenIntrospector introspector;
|
||||
|
||||
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenReactiveAuthenticationManager::convert;
|
||||
|
||||
/**
|
||||
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
|
||||
@ -73,20 +72,16 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
||||
public OpaqueTokenReactiveAuthenticationManager(ReactiveOpaqueTokenIntrospector introspector) {
|
||||
Assert.notNull(introspector, "introspector cannot be null");
|
||||
this.introspector = introspector;
|
||||
this.setAuthenticationConverter(OpaqueTokenReactiveAuthenticationManager::convert);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Introspect and validate the opaque
|
||||
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
||||
* Token</a> and then delegates {@link Authentication} instantiation to
|
||||
* {@link OpaqueTokenAuthenticationConverter}.
|
||||
* </p>
|
||||
* {@link ReactiveOpaqueTokenAuthenticationConverter}.
|
||||
* <p>
|
||||
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||
* details are null, then introspection result details are used.
|
||||
* </p>
|
||||
* @param authentication the authentication request object.
|
||||
* @return A successful authentication
|
||||
*/
|
||||
@ -117,10 +112,10 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
||||
}
|
||||
|
||||
/**
|
||||
* Default reactive {@link OpaqueTokenAuthenticationConverter}.
|
||||
* @param introspectedToken the bearer sring that was successfuly introspected
|
||||
* Default {@link ReactiveOpaqueTokenAuthenticationConverter}.
|
||||
* @param introspectedToken the bearer string that was successfully introspected
|
||||
* @param authenticatedPrincipal the successful introspection output
|
||||
* @returna an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
|
||||
* @return an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
|
||||
* result
|
||||
*/
|
||||
static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
|
@ -20,7 +20,7 @@ import org.springframework.security.core.Authentication;
|
||||
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 <ch4mp@c4-soft.com>
|
||||
* @since 5.8
|
||||
@ -28,6 +28,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
@FunctionalInterface
|
||||
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);
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import org.springframework.security.core.Authentication;
|
||||
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 <ch4mp@c4-soft.com>
|
||||
* @since 5.8
|
||||
@ -30,6 +30,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
@FunctionalInterface
|
||||
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);
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import java.util.Map;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
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.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
|
||||
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 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.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpaqueTokenAuthenticationProvider}
|
||||
@ -114,4 +118,33 @@ public class OpaqueTokenAuthenticationProviderTests {
|
||||
// @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
* 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 org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
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.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
|
||||
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 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.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
/**
|
||||
* Tests for {@link OpaqueTokenReactiveAuthenticationManager}
|
||||
@ -112,4 +116,34 @@ public class OpaqueTokenReactiveAuthenticationManagerTests {
|
||||
// @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);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user