Merge branch '5.8.x'
This commit is contained in:
commit
2431dd1103
|
@ -44,6 +44,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
|
||||
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
|
||||
|
@ -105,8 +106,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint and its
|
||||
* authentication configuration
|
||||
* When using {@link #opaqueToken(Customizer)}, supply an introspection endpoint with its
|
||||
* client credentials and an OpaqueTokenAuthenticationConverter
|
||||
* </p>
|
||||
*
|
||||
* <h2>Security Filters</h2>
|
||||
|
@ -136,6 +137,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
|
|||
*
|
||||
* @author Josh Cummings
|
||||
* @author Evgeniy Cheban
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
* @since 5.1
|
||||
* @see BearerTokenAuthenticationFilter
|
||||
* @see JwtAuthenticationProvider
|
||||
|
@ -448,6 +450,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||
|
||||
private Supplier<OpaqueTokenIntrospector> introspector;
|
||||
|
||||
private OpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
|
||||
OpaqueTokenConfigurer(ApplicationContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
@ -482,6 +486,13 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||
return this;
|
||||
}
|
||||
|
||||
public OpaqueTokenConfigurer authenticationConverter(
|
||||
OpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
OpaqueTokenIntrospector getIntrospector() {
|
||||
if (this.introspector != null) {
|
||||
return this.introspector.get();
|
||||
|
@ -489,12 +500,28 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
|
|||
return this.context.getBean(OpaqueTokenIntrospector.class);
|
||||
}
|
||||
|
||||
OpaqueTokenAuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter != null) {
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
if (this.context.getBeanNamesForType(OpaqueTokenAuthenticationConverter.class).length > 0) {
|
||||
return this.context.getBean(OpaqueTokenAuthenticationConverter.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
AuthenticationProvider getAuthenticationProvider() {
|
||||
if (this.authenticationManager != null) {
|
||||
return null;
|
||||
}
|
||||
OpaqueTokenIntrospector introspector = getIntrospector();
|
||||
return new OpaqueTokenAuthenticationProvider(introspector);
|
||||
OpaqueTokenAuthenticationProvider opaqueTokenAuthenticationProvider = new OpaqueTokenAuthenticationProvider(
|
||||
introspector);
|
||||
OpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
|
||||
if (authenticationConverter != null) {
|
||||
opaqueTokenAuthenticationProvider.setAuthenticationConverter(authenticationConverter);
|
||||
}
|
||||
return opaqueTokenAuthenticationProvider;
|
||||
}
|
||||
|
||||
AuthenticationManager getAuthenticationManager(H http) {
|
||||
|
|
|
@ -244,6 +244,10 @@ 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() {
|
||||
}
|
||||
|
||||
|
@ -251,9 +255,13 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
|
|||
public BeanDefinition parse(Element element, ParserContext pc) {
|
||||
validateConfiguration(element, pc);
|
||||
BeanMetadataElement introspector = getIntrospector(element);
|
||||
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
|
||||
BeanDefinitionBuilder opaqueTokenProviderBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(OpaqueTokenAuthenticationProvider.class);
|
||||
opaqueTokenProviderBuilder.addConstructorArgValue(introspector);
|
||||
if (StringUtils.hasText(authenticationConverterRef)) {
|
||||
opaqueTokenProviderBuilder.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.
|
||||
|
@ -95,6 +95,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtRea
|
|||
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.NimbusReactiveOpaqueTokenIntrospector;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||
import org.springframework.security.oauth2.server.resource.web.access.server.BearerTokenServerAccessDeniedHandler;
|
||||
import org.springframework.security.oauth2.server.resource.web.server.BearerTokenServerAuthenticationEntryPoint;
|
||||
|
@ -4285,6 +4286,8 @@ public class ServerHttpSecurity {
|
|||
|
||||
private Supplier<ReactiveOpaqueTokenIntrospector> introspector;
|
||||
|
||||
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter;
|
||||
|
||||
private OpaqueTokenSpec() {
|
||||
}
|
||||
|
||||
|
@ -4323,6 +4326,13 @@ public class ServerHttpSecurity {
|
|||
return this;
|
||||
}
|
||||
|
||||
public OpaqueTokenSpec authenticationConverter(
|
||||
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows method chaining to continue configuring the
|
||||
* {@link ServerHttpSecurity}
|
||||
|
@ -4333,7 +4343,13 @@ public class ServerHttpSecurity {
|
|||
}
|
||||
|
||||
protected ReactiveAuthenticationManager getAuthenticationManager() {
|
||||
return new OpaqueTokenReactiveAuthenticationManager(getIntrospector());
|
||||
OpaqueTokenReactiveAuthenticationManager authenticationManager = new OpaqueTokenReactiveAuthenticationManager(
|
||||
getIntrospector());
|
||||
ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = getAuthenticationConverter();
|
||||
if (authenticationConverter != null) {
|
||||
authenticationManager.setAuthenticationConverter(authenticationConverter);
|
||||
}
|
||||
return authenticationManager;
|
||||
}
|
||||
|
||||
protected ReactiveOpaqueTokenIntrospector getIntrospector() {
|
||||
|
@ -4343,6 +4359,13 @@ public class ServerHttpSecurity {
|
|||
return getBean(ReactiveOpaqueTokenIntrospector.class);
|
||||
}
|
||||
|
||||
protected ReactiveOpaqueTokenAuthenticationConverter getAuthenticationConverter() {
|
||||
if (this.authenticationConverter != null) {
|
||||
return this.authenticationConverter;
|
||||
}
|
||||
return getBeanOrNull(ReactiveOpaqueTokenAuthenticationConverter.class);
|
||||
}
|
||||
|
||||
protected void configure(ServerHttpSecurity http) {
|
||||
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
|
||||
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
|
||||
|
|
|
@ -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.
|
||||
|
@ -20,6 +20,7 @@ import org.springframework.security.authentication.AuthenticationManager
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector
|
||||
|
||||
/**
|
||||
|
@ -54,6 +55,7 @@ class OpaqueTokenDsl {
|
|||
clientCredentials = null
|
||||
}
|
||||
|
||||
var authenticationConverter: OpaqueTokenAuthenticationConverter? = null
|
||||
|
||||
/**
|
||||
* Configures the credentials for Introspection endpoint.
|
||||
|
@ -70,6 +72,7 @@ class OpaqueTokenDsl {
|
|||
return { opaqueToken ->
|
||||
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
||||
introspector?.also { opaqueToken.introspector(introspector) }
|
||||
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
|
||||
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
||||
authenticationManager?.also { opaqueToken.authenticationManager(authenticationManager) }
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -16,6 +16,7 @@
|
|||
|
||||
package org.springframework.security.config.web.server
|
||||
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector
|
||||
|
||||
/**
|
||||
|
@ -45,6 +46,7 @@ class ServerOpaqueTokenDsl {
|
|||
_introspectionUri = null
|
||||
clientCredentials = null
|
||||
}
|
||||
var authenticationConverter: ReactiveOpaqueTokenAuthenticationConverter? = null
|
||||
|
||||
/**
|
||||
* Configures the credentials for Introspection endpoint.
|
||||
|
@ -62,6 +64,7 @@ class ServerOpaqueTokenDsl {
|
|||
introspectionUri?.also { opaqueToken.introspectionUri(introspectionUri) }
|
||||
clientCredentials?.also { opaqueToken.introspectionClientCredentials(clientCredentials!!.first, clientCredentials!!.second) }
|
||||
introspector?.also { opaqueToken.introspector(introspector) }
|
||||
authenticationConverter?.also { opaqueToken.authenticationConverter(authenticationConverter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -667,6 +667,9 @@ opaque-token.attlist &=
|
|||
opaque-token.attlist &=
|
||||
## Reference to an OpaqueTokenIntrospector
|
||||
attribute introspector-ref {xsd:token}?
|
||||
opaque-token.attlist &=
|
||||
## Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful introspection result into an Authentication.
|
||||
attribute authentication-converter-ref {xsd:token}?
|
||||
|
||||
openid-login =
|
||||
## Sets up form login for authentication with an Open ID identity. NOTE: The OpenID 1.0 and 2.0 protocols have been deprecated and users are <a href="https://openid.net/specs/openid-connect-migration-1_0.html">encouraged to migrate</a> to <a href="https://openid.net/connect/">OpenID Connect</a>, which is supported by <code>spring-security-oauth2</code>.
|
||||
|
|
|
@ -2060,6 +2060,13 @@
|
|||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="authentication-converter-ref" type="xs:token">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Reference to an OpaqueTokenAuthenticationConverter responsible for converting successful
|
||||
introspection result into an Authentication.
|
||||
</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:attributeGroup>
|
||||
|
||||
<xs:element name="attribute-exchange">
|
||||
|
|
|
@ -80,6 +80,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.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
|
||||
|
@ -98,6 +99,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;
|
||||
|
@ -116,6 +118,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;
|
||||
|
@ -1353,6 +1356,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));
|
||||
}
|
||||
|
@ -2444,6 +2463,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 {
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.security.interfaces.RSAPublicKey;
|
|||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -66,12 +67,16 @@ import org.springframework.http.HttpStatus;
|
|||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationManagerResolver;
|
||||
import org.springframework.security.authentication.AuthenticationServiceException;
|
||||
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.JwtBeanDefinitionParser;
|
||||
import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser;
|
||||
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.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||
|
@ -84,6 +89,7 @@ import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|||
import org.springframework.security.oauth2.jwt.TestJwts;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
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.BearerTokenResolver;
|
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
|
||||
|
@ -643,6 +649,20 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenIntrospectingWithAuthenticationConverterThenUses() throws Exception {
|
||||
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueTokenAndAuthenticationConverter"))
|
||||
.autowire();
|
||||
mockRestOperations(json("Active"));
|
||||
// @formatter:off
|
||||
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token"))
|
||||
.andExpect(status().isNotFound());
|
||||
|
||||
this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalidToken"))
|
||||
.andExpect(status().isUnauthorized());
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenIntrospectionFailsThenUnauthorized() throws Exception {
|
||||
this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire();
|
||||
|
@ -1077,4 +1097,39 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
|
|||
|
||||
}
|
||||
|
||||
public static class TestAuthentication extends AbstractAuthenticationToken {
|
||||
|
||||
private final String introspectedToken;
|
||||
|
||||
public TestAuthentication(String introspectedToken, Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.introspectedToken = introspectedToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.introspectedToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.introspectedToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return "token".equals(this.introspectedToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestOpaqueTokenAuthenticationConverter implements OpaqueTokenAuthenticationConverter {
|
||||
|
||||
@Override
|
||||
public Authentication convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
return new TestAuthentication(introspectedToken, Collections.emptyList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -566,6 +570,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
|
||||
|
@ -1052,6 +1075,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 {
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2002-2020 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.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ https://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://www.springframework.org/schema/security"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
|
||||
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<b:bean name="authentication-converter"
|
||||
class="org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParserTests$TestOpaqueTokenAuthenticationConverter">
|
||||
</b:bean>
|
||||
|
||||
<http>
|
||||
<intercept-url pattern="/requires-read-scope" access="hasAuthority('SCOPE_message:read')"/>
|
||||
<intercept-url pattern="/**" access="authenticated"/>
|
||||
<oauth2-resource-server>
|
||||
<opaque-token introspector-ref="introspector" authentication-converter-ref="authentication-converter"/>
|
||||
</oauth2-resource-server>
|
||||
</http>
|
||||
|
||||
<b:import resource="userservice.xml"/>
|
||||
</b:beans>
|
|
@ -1324,6 +1324,10 @@ The Client Id to use for client authentication against the provided `introspecti
|
|||
* **client-secret**
|
||||
The Client Secret to use for client authentication against the provided `introspection-uri`.
|
||||
|
||||
[[nsa-opaque-token-authentication-converter-ref]]
|
||||
* **authentication-converter-ref**
|
||||
Reference to an `OpaqueTokenAuthenticationConverter`. Responsible for converting successful introspection result into an `Authentication` instance.
|
||||
|
||||
|
||||
[[nsa-relying-party-registrations]]
|
||||
== <relying-party-registrations>
|
||||
|
|
|
@ -297,11 +297,13 @@ fun introspector(): OpaqueTokenIntrospector {
|
|||
----
|
||||
====
|
||||
|
||||
If the application doesn't expose a <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> bean, then Spring Boot will expose the above default one.
|
||||
If the application doesn't expose an <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> bean, then Spring Boot will expose the above default one.
|
||||
|
||||
And its configuration can be overridden using `introspectionUri()` and `introspectionClientCredentials()` or replaced using `introspector()`.
|
||||
|
||||
Or, if you're not using Spring Boot at all, then both of these components - the filter chain and a <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> can be specified in XML.
|
||||
If the application doesn't expose an `OpaqueTokenAuthenticationConverter` bean, then spring-security will build `BearerTokenAuthentication`.
|
||||
|
||||
Or, if you're not using Spring Boot at all, then all of these components - the filter chain, an <<oauth2resourceserver-opaque-architecture-introspector,`OpaqueTokenIntrospector`>> and an `OpaqueTokenAuthenticationConverter` can be specified in XML.
|
||||
|
||||
The filter chain is specified like so:
|
||||
|
||||
|
@ -313,7 +315,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 +338,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.
|
||||
|
@ -35,6 +35,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenAuthentica
|
|||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
||||
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.util.Assert;
|
||||
|
||||
|
@ -57,8 +58,16 @@ import org.springframework.util.Assert;
|
|||
* <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>
|
||||
* 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>
|
||||
* @since 5.2
|
||||
* @see AuthenticationProvider
|
||||
*/
|
||||
|
@ -68,6 +77,8 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||
|
||||
private final OpaqueTokenIntrospector introspector;
|
||||
|
||||
private OpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenAuthenticationProvider::convert;
|
||||
|
||||
/**
|
||||
* Creates a {@code OpaqueTokenAuthenticationProvider} with the provided parameters
|
||||
* @param introspector The {@link OpaqueTokenIntrospector} to use
|
||||
|
@ -80,7 +91,11 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||
/**
|
||||
* Introspect and validate the opaque
|
||||
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
|
||||
* Token</a>.
|
||||
* Token</a> and then delegates {@link Authentication} instantiation to
|
||||
* {@link OpaqueTokenAuthenticationConverter}.
|
||||
* <p>
|
||||
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||
* details are null, then introspection result details are used.
|
||||
* @param authentication the authentication request object.
|
||||
* @return A successful authentication
|
||||
* @throws AuthenticationException if authentication failed for some reason
|
||||
|
@ -92,8 +107,16 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||
}
|
||||
BearerTokenAuthenticationToken bearer = (BearerTokenAuthenticationToken) authentication;
|
||||
OAuth2AuthenticatedPrincipal principal = getOAuth2AuthenticatedPrincipal(bearer);
|
||||
AbstractAuthenticationToken result = convert(principal, bearer.getToken());
|
||||
result.setDetails(bearer.getDetails());
|
||||
Authentication result = this.authenticationConverter.convert(bearer.getToken(), principal);
|
||||
if (result == null) {
|
||||
return null;
|
||||
}
|
||||
if (AbstractAuthenticationToken.class.isAssignableFrom(result.getClass())) {
|
||||
final AbstractAuthenticationToken auth = (AbstractAuthenticationToken) result;
|
||||
if (auth.getDetails() == null) {
|
||||
auth.setDetails(bearer.getDetails());
|
||||
}
|
||||
}
|
||||
this.logger.debug("Authenticated token");
|
||||
return result;
|
||||
}
|
||||
|
@ -116,11 +139,32 @@ public final class OpaqueTokenAuthenticationProvider implements AuthenticationPr
|
|||
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private AbstractAuthenticationToken convert(OAuth2AuthenticatedPrincipal principal, String token) {
|
||||
Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
||||
Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
||||
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
||||
/**
|
||||
* Default {@link OpaqueTokenAuthenticationConverter}.
|
||||
* @param introspectedToken the bearer string that was successfully introspected
|
||||
* @param authenticatedPrincipal the successful introspection output
|
||||
* @return a {@link BearerTokenAuthentication}
|
||||
*/
|
||||
static BearerTokenAuthentication convert(String introspectedToken,
|
||||
OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
Instant iat = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
||||
Instant exp = authenticatedPrincipal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, introspectedToken,
|
||||
iat, exp);
|
||||
return new BearerTokenAuthentication(authenticatedPrincipal, accessToken,
|
||||
authenticatedPrincipal.getAuthorities());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide with a custom bean to turn successful introspection result into an
|
||||
* {@link Authentication} instance of your choice. By default,
|
||||
* {@link BearerTokenAuthentication} will be built.
|
||||
* @param authenticationConverter the converter to use
|
||||
* @since 5.8
|
||||
*/
|
||||
public void setAuthenticationConverter(OpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = 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.
|
||||
|
@ -16,22 +16,21 @@
|
|||
|
||||
package org.springframework.security.oauth2.server.resource.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
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;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.BadOpaqueTokenException;
|
||||
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.ReactiveOpaqueTokenAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
@ -46,16 +45,16 @@ import org.springframework.util.Assert;
|
|||
* verifying an opaque access token, returning its attributes set as part of the
|
||||
* {@link Authentication} statement.
|
||||
* <p>
|
||||
* 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>
|
||||
* A {@link ReactiveOpaqueTokenIntrospector} is responsible for retrieving token
|
||||
* attributes from an authorization server.
|
||||
* <p>
|
||||
* 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>
|
||||
* @since 5.2
|
||||
* @see ReactiveAuthenticationManager
|
||||
*/
|
||||
|
@ -63,6 +62,8 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||
|
||||
private final ReactiveOpaqueTokenIntrospector introspector;
|
||||
|
||||
private ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = OpaqueTokenReactiveAuthenticationManager::convert;
|
||||
|
||||
/**
|
||||
* Creates a {@code OpaqueTokenReactiveAuthenticationManager} with the provided
|
||||
* parameters
|
||||
|
@ -73,6 +74,17 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||
this.introspector = introspector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ReactiveOpaqueTokenAuthenticationConverter}.
|
||||
* <p>
|
||||
* If created Authentication is instance of {@link AbstractAuthenticationToken} and
|
||||
* details are null, then introspection result details are used.
|
||||
* @param authentication the authentication request object.
|
||||
* @return A successful authentication
|
||||
*/
|
||||
@Override
|
||||
public Mono<Authentication> authenticate(Authentication authentication) {
|
||||
// @formatter:off
|
||||
|
@ -80,21 +92,14 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||
.filter(BearerTokenAuthenticationToken.class::isInstance)
|
||||
.cast(BearerTokenAuthenticationToken.class)
|
||||
.map(BearerTokenAuthenticationToken::getToken)
|
||||
.flatMap(this::authenticate)
|
||||
.cast(Authentication.class);
|
||||
.flatMap(this::authenticate);
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private Mono<BearerTokenAuthentication> authenticate(String token) {
|
||||
private Mono<Authentication> authenticate(String token) {
|
||||
// @formatter:off
|
||||
return this.introspector.introspect(token)
|
||||
.map((principal) -> {
|
||||
Instant iat = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.IAT);
|
||||
Instant exp = principal.getAttribute(OAuth2TokenIntrospectionClaimNames.EXP);
|
||||
// construct token
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token, iat, exp);
|
||||
return new BearerTokenAuthentication(principal, accessToken, principal.getAuthorities());
|
||||
})
|
||||
.flatMap((principal) -> this.authenticationConverter.convert(token, principal))
|
||||
.onErrorMap(OAuth2IntrospectionException.class, this::onError);
|
||||
// @formatter:on
|
||||
}
|
||||
|
@ -106,4 +111,27 @@ public class OpaqueTokenReactiveAuthenticationManager implements ReactiveAuthent
|
|||
return new AuthenticationServiceException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default {@link ReactiveOpaqueTokenAuthenticationConverter}.
|
||||
* @param introspectedToken the bearer string that was successfully introspected
|
||||
* @param authenticatedPrincipal the successful introspection output
|
||||
* @return an async wrapper of default {@link OpaqueTokenAuthenticationConverter}
|
||||
* result
|
||||
*/
|
||||
static Mono<Authentication> convert(String introspectedToken, OAuth2AuthenticatedPrincipal authenticatedPrincipal) {
|
||||
return Mono.just(OpaqueTokenAuthenticationProvider.convert(introspectedToken, authenticatedPrincipal));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide with a custom bean to turn successful introspection result into an
|
||||
* {@link Authentication} instance of your choice. By default,
|
||||
* {@link BearerTokenAuthentication} will be built.
|
||||
* @param authenticationConverter the converter to use
|
||||
* @since 5.8
|
||||
*/
|
||||
public void setAuthenticationConverter(ReactiveOpaqueTokenAuthenticationConverter authenticationConverter) {
|
||||
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
|
||||
this.authenticationConverter = authenticationConverter;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.server.resource.introspection;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
|
||||
/**
|
||||
* Convert a successful introspection result into an authentication result.
|
||||
*
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
* @since 5.8
|
||||
*/
|
||||
@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);
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.oauth2.server.resource.introspection;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
|
||||
|
||||
/**
|
||||
* Convert a successful introspection result into an authentication result.
|
||||
*
|
||||
* @author Jerome Wacongne <ch4mp@c4-soft.com>
|
||||
* @since 5.8
|
||||
*/
|
||||
@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);
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -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…
Reference in New Issue