Merge pull request #38105 from ykardziyaka
* pr/38105: Polish "Auto-configure a JwtAuthenticationConverter" Auto-configure a JwtAuthenticationConverter Closes gh-38105
This commit is contained in:
commit
1000733a4d
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StreamUtils;
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ import org.springframework.util.StreamUtils;
|
||||||
* @author Madhura Bhave
|
* @author Madhura Bhave
|
||||||
* @author Artsiom Yudovin
|
* @author Artsiom Yudovin
|
||||||
* @author Mushtaq Ahmed
|
* @author Mushtaq Ahmed
|
||||||
|
* @author Yan Kardziyaka
|
||||||
* @since 2.1.0
|
* @since 2.1.0
|
||||||
*/
|
*/
|
||||||
@ConfigurationProperties(prefix = "spring.security.oauth2.resourceserver")
|
@ConfigurationProperties(prefix = "spring.security.oauth2.resourceserver")
|
||||||
|
@ -80,6 +82,28 @@ public class OAuth2ResourceServerProperties {
|
||||||
*/
|
*/
|
||||||
private List<String> audiences = new ArrayList<>();
|
private List<String> audiences = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix to use for {@link GrantedAuthority authorities} mapped from JWT.
|
||||||
|
*/
|
||||||
|
private String authorityPrefix;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regex to use for splitting the value of the authorities claim into
|
||||||
|
* {@link GrantedAuthority authorities}.
|
||||||
|
*/
|
||||||
|
private String authoritiesClaimDelimiter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of token claim to use for mapping {@link GrantedAuthority authorities}
|
||||||
|
* from JWT.
|
||||||
|
*/
|
||||||
|
private String authoritiesClaimName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT principal claim name.
|
||||||
|
*/
|
||||||
|
private String principalClaimName;
|
||||||
|
|
||||||
public String getJwkSetUri() {
|
public String getJwkSetUri() {
|
||||||
return this.jwkSetUri;
|
return this.jwkSetUri;
|
||||||
}
|
}
|
||||||
|
@ -120,6 +144,38 @@ public class OAuth2ResourceServerProperties {
|
||||||
this.audiences = audiences;
|
this.audiences = audiences;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAuthorityPrefix() {
|
||||||
|
return this.authorityPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorityPrefix(String authorityPrefix) {
|
||||||
|
this.authorityPrefix = authorityPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthoritiesClaimDelimiter() {
|
||||||
|
return this.authoritiesClaimDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthoritiesClaimDelimiter(String authoritiesClaimDelimiter) {
|
||||||
|
this.authoritiesClaimDelimiter = authoritiesClaimDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthoritiesClaimName() {
|
||||||
|
return this.authoritiesClaimName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthoritiesClaimName(String authoritiesClaimName) {
|
||||||
|
this.authoritiesClaimName = authoritiesClaimName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPrincipalClaimName() {
|
||||||
|
return this.principalClaimName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrincipalClaimName(String principalClaimName) {
|
||||||
|
this.principalClaimName = principalClaimName;
|
||||||
|
}
|
||||||
|
|
||||||
public String readPublicKey() throws IOException {
|
public String readPublicKey() throws IOException {
|
||||||
String key = "spring.security.oauth2.resourceserver.public-key-location";
|
String key = "spring.security.oauth2.resourceserver.public-key-location";
|
||||||
Assert.notNull(this.publicKeyLocation, "PublicKeyLocation must not be null");
|
Assert.notNull(this.publicKeyLocation, "PublicKeyLocation must not be null");
|
||||||
|
|
|
@ -34,6 +34,7 @@ class ReactiveOAuth2ResourceServerConfiguration {
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
|
@ConditionalOnClass({ BearerTokenAuthenticationToken.class, ReactiveJwtDecoder.class })
|
||||||
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
|
@Import({ ReactiveOAuth2ResourceServerJwkConfiguration.JwtConfiguration.class,
|
||||||
|
ReactiveOAuth2ResourceServerJwkConfiguration.JwtConverterConfiguration.class,
|
||||||
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
|
ReactiveOAuth2ResourceServerJwkConfiguration.WebSecurityConfiguration.class })
|
||||||
static class JwtConfiguration {
|
static class JwtConfiguration {
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,14 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
||||||
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -48,6 +50,9 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder;
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtGrantedAuthoritiesConverterAdapter;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
@ -62,6 +67,7 @@ import org.springframework.util.CollectionUtils;
|
||||||
* @author Anastasiia Losieva
|
* @author Anastasiia Losieva
|
||||||
* @author Mushtaq Ahmed
|
* @author Mushtaq Ahmed
|
||||||
* @author Roman Golovin
|
* @author Roman Golovin
|
||||||
|
* @author Yan Kardziyaka
|
||||||
*/
|
*/
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
class ReactiveOAuth2ResourceServerJwkConfiguration {
|
class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||||
|
@ -161,6 +167,35 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnMissingBean(ReactiveJwtAuthenticationConverter.class)
|
||||||
|
@Conditional(JwtConverterPropertiesCondition.class)
|
||||||
|
static class JwtConverterConfiguration {
|
||||||
|
|
||||||
|
private final OAuth2ResourceServerProperties.Jwt properties;
|
||||||
|
|
||||||
|
JwtConverterConfiguration(OAuth2ResourceServerProperties properties) {
|
||||||
|
this.properties = properties.getJwt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ReactiveJwtAuthenticationConverter reactiveJwtAuthenticationConverter() {
|
||||||
|
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||||
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
|
map.from(this.properties.getAuthorityPrefix()).to(grantedAuthoritiesConverter::setAuthorityPrefix);
|
||||||
|
map.from(this.properties.getAuthoritiesClaimDelimiter())
|
||||||
|
.to(grantedAuthoritiesConverter::setAuthoritiesClaimDelimiter);
|
||||||
|
map.from(this.properties.getAuthoritiesClaimName())
|
||||||
|
.to(grantedAuthoritiesConverter::setAuthoritiesClaimName);
|
||||||
|
ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
|
||||||
|
map.from(this.properties.getPrincipalClaimName()).to(jwtAuthenticationConverter::setPrincipalClaimName);
|
||||||
|
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
|
||||||
|
new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter));
|
||||||
|
return jwtAuthenticationConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnMissingBean(SecurityWebFilterChain.class)
|
@ConditionalOnMissingBean(SecurityWebFilterChain.class)
|
||||||
static class WebSecurityConfiguration {
|
static class WebSecurityConfiguration {
|
||||||
|
@ -179,4 +214,27 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class JwtConverterPropertiesCondition extends AnyNestedCondition {
|
||||||
|
|
||||||
|
JwtConverterPropertiesCondition() {
|
||||||
|
super(ConfigurationPhase.REGISTER_BEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix")
|
||||||
|
static class OnAuthorityPrefix {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name")
|
||||||
|
static class OnPrincipalClaimName {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name")
|
||||||
|
static class OnAuthoritiesClaimName {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
@ -33,6 +34,7 @@ import org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSe
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.KeyValueCondition;
|
||||||
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
|
||||||
|
import org.springframework.boot.context.properties.PropertyMapper;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Conditional;
|
import org.springframework.context.annotation.Conditional;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -48,6 +50,8 @@ import org.springframework.security.oauth2.jwt.JwtValidators;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder;
|
||||||
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||||
|
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
@ -63,6 +67,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
* @author HaiTao Zhang
|
* @author HaiTao Zhang
|
||||||
* @author Mushtaq Ahmed
|
* @author Mushtaq Ahmed
|
||||||
* @author Roman Golovin
|
* @author Roman Golovin
|
||||||
|
* @author Yan Kardziyaka
|
||||||
*/
|
*/
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
class OAuth2ResourceServerJwtConfiguration {
|
class OAuth2ResourceServerJwtConfiguration {
|
||||||
|
@ -173,4 +178,55 @@ class OAuth2ResourceServerJwtConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
@ConditionalOnMissingBean(JwtAuthenticationConverter.class)
|
||||||
|
@Conditional(JwtConverterPropertiesCondition.class)
|
||||||
|
static class JwtConverterConfiguration {
|
||||||
|
|
||||||
|
private final OAuth2ResourceServerProperties.Jwt properties;
|
||||||
|
|
||||||
|
JwtConverterConfiguration(OAuth2ResourceServerProperties properties) {
|
||||||
|
this.properties = properties.getJwt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
JwtAuthenticationConverter getJwtAuthenticationConverter() {
|
||||||
|
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||||
|
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||||
|
map.from(this.properties.getAuthorityPrefix()).to(grantedAuthoritiesConverter::setAuthorityPrefix);
|
||||||
|
map.from(this.properties.getAuthoritiesClaimDelimiter())
|
||||||
|
.to(grantedAuthoritiesConverter::setAuthoritiesClaimDelimiter);
|
||||||
|
map.from(this.properties.getAuthoritiesClaimName())
|
||||||
|
.to(grantedAuthoritiesConverter::setAuthoritiesClaimName);
|
||||||
|
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
|
||||||
|
map.from(this.properties.getPrincipalClaimName()).to(jwtAuthenticationConverter::setPrincipalClaimName);
|
||||||
|
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
|
||||||
|
return jwtAuthenticationConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class JwtConverterPropertiesCondition extends AnyNestedCondition {
|
||||||
|
|
||||||
|
JwtConverterPropertiesCondition() {
|
||||||
|
super(ConfigurationPhase.REGISTER_BEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authority-prefix")
|
||||||
|
static class OnAuthorityPrefix {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "principal-claim-name")
|
||||||
|
static class OnPrincipalClaimName {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConditionalOnProperty(prefix = "spring.security.oauth2.resourceserver.jwt", name = "authorities-claim-name")
|
||||||
|
static class OnAuthoritiesClaimName {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ class Oauth2ResourceServerConfiguration {
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
@ConditionalOnClass(JwtDecoder.class)
|
@ConditionalOnClass(JwtDecoder.class)
|
||||||
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
|
@Import({ OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration.class,
|
||||||
OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration.class })
|
OAuth2ResourceServerJwtConfiguration.OAuth2SecurityFilterChainConfiguration.class,
|
||||||
|
OAuth2ResourceServerJwtConfiguration.JwtConverterConfiguration.class })
|
||||||
static class JwtConfiguration {
|
static class JwtConfiguration {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2012-2023 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.boot.autoconfigure.security.oauth2.resource;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Named;
|
||||||
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ArgumentsProvider Arguments provider} supplying different Spring Boot properties
|
||||||
|
* to customize JWT converter behavior, JWT token for conversion, expected principal name
|
||||||
|
* and expected authorities.
|
||||||
|
*
|
||||||
|
* @author Yan Kardziyaka
|
||||||
|
*/
|
||||||
|
public final class JwtConverterCustomizationsArgumentsProvider implements ArgumentsProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) {
|
||||||
|
String customPrefix = "CUSTOM_AUTHORITY_PREFIX_";
|
||||||
|
String customDelimiter = "[~,#:]";
|
||||||
|
String customAuthoritiesClaim = "custom_authorities";
|
||||||
|
String customPrincipalClaim = "custom_principal";
|
||||||
|
String jwkSetUriProperty = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com";
|
||||||
|
String authorityPrefixProperty = "spring.security.oauth2.resourceserver.jwt.authority-prefix=" + customPrefix;
|
||||||
|
String authoritiesDelimiterProperty = "spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter="
|
||||||
|
+ customDelimiter;
|
||||||
|
String authoritiesClaimProperty = "spring.security.oauth2.resourceserver.jwt.authorities-claim-name="
|
||||||
|
+ customAuthoritiesClaim;
|
||||||
|
String principalClaimProperty = "spring.security.oauth2.resourceserver.jwt.principal-claim-name="
|
||||||
|
+ customPrincipalClaim;
|
||||||
|
String[] customPrefixProps = { jwkSetUriProperty, authorityPrefixProperty };
|
||||||
|
String[] customDelimiterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty };
|
||||||
|
String[] customAuthoritiesClaimProps = { jwkSetUriProperty, authoritiesClaimProperty };
|
||||||
|
String[] customPrincipalClaimProps = { jwkSetUriProperty, principalClaimProperty };
|
||||||
|
String[] allJwtConverterProps = { jwkSetUriProperty, authorityPrefixProperty, authoritiesDelimiterProperty,
|
||||||
|
authoritiesClaimProperty, principalClaimProperty };
|
||||||
|
String[] jwtScopes = { "custom_scope0", "custom_scope1" };
|
||||||
|
String subjectValue = UUID.randomUUID().toString();
|
||||||
|
String customPrincipalValue = UUID.randomUUID().toString();
|
||||||
|
Jwt.Builder jwtBuilder = Jwt.withTokenValue("token")
|
||||||
|
.header("alg", "none")
|
||||||
|
.expiresAt(Instant.MAX)
|
||||||
|
.issuedAt(Instant.MIN)
|
||||||
|
.issuer("https://issuer.example.org")
|
||||||
|
.jti("jti")
|
||||||
|
.notBefore(Instant.MIN)
|
||||||
|
.subject(subjectValue)
|
||||||
|
.claim(customPrincipalClaim, customPrincipalValue);
|
||||||
|
Jwt noAuthoritiesCustomizationsJwt = jwtBuilder.claim("scp", jwtScopes[0] + " " + jwtScopes[1]).build();
|
||||||
|
Jwt customAuthoritiesDelimiterJwt = jwtBuilder.claim("scp", jwtScopes[0] + "~" + jwtScopes[1]).build();
|
||||||
|
Jwt customAuthoritiesClaimJwt = jwtBuilder.claim("scp", null)
|
||||||
|
.claim(customAuthoritiesClaim, jwtScopes[0] + " " + jwtScopes[1])
|
||||||
|
.build();
|
||||||
|
Jwt customAuthoritiesClaimAndDelimiterJwt = jwtBuilder.claim("scp", null)
|
||||||
|
.claim(customAuthoritiesClaim, jwtScopes[0] + "~" + jwtScopes[1])
|
||||||
|
.build();
|
||||||
|
String[] customPrefixAuthorities = { customPrefix + jwtScopes[0], customPrefix + jwtScopes[1] };
|
||||||
|
String[] defaultPrefixAuthorities = { "SCOPE_" + jwtScopes[0], "SCOPE_" + jwtScopes[1] };
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(Named.named("Custom prefix for GrantedAuthority", customPrefixProps),
|
||||||
|
noAuthoritiesCustomizationsJwt, subjectValue, customPrefixAuthorities),
|
||||||
|
Arguments.of(Named.named("Custom delimiter for JWT scopes", customDelimiterProps),
|
||||||
|
customAuthoritiesDelimiterJwt, subjectValue, customPrefixAuthorities),
|
||||||
|
Arguments.of(Named.named("Custom JWT authority claim name", customAuthoritiesClaimProps),
|
||||||
|
customAuthoritiesClaimJwt, subjectValue, defaultPrefixAuthorities),
|
||||||
|
Arguments.of(Named.named("Custom JWT principal claim name", customPrincipalClaimProps),
|
||||||
|
noAuthoritiesCustomizationsJwt, customPrincipalValue, defaultPrefixAuthorities),
|
||||||
|
Arguments.of(Named.named("All JWT converter customizations", allJwtConverterProps),
|
||||||
|
customAuthoritiesClaimAndDelimiterJwt, customPrincipalValue, customPrefixAuthorities));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -39,10 +39,13 @@ import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
import org.assertj.core.api.ThrowingConsumer;
|
import org.assertj.core.api.ThrowingConsumer;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtConverterCustomizationsArgumentsProvider;
|
||||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||||
|
@ -52,10 +55,12 @@ import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
|
||||||
import org.springframework.security.config.BeanIds;
|
import org.springframework.security.config.BeanIds;
|
||||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
|
||||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
|
@ -70,6 +75,7 @@ import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
|
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.ReactiveOpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||||
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
|
@ -92,6 +98,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
* @author Anastasiia Losieva
|
* @author Anastasiia Losieva
|
||||||
* @author Mushtaq Ahmed
|
* @author Mushtaq Ahmed
|
||||||
* @author Roman Golovin
|
* @author Roman Golovin
|
||||||
|
* @author Yan Kardziyaka
|
||||||
*/
|
*/
|
||||||
class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
|
|
||||||
|
@ -626,6 +633,70 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() {
|
||||||
|
this.contextRunner
|
||||||
|
.run((context) -> assertThat(context).doesNotHaveBean(ReactiveJwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}")
|
||||||
|
@ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class)
|
||||||
|
void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt,
|
||||||
|
String expectedPrincipal, String[] expectedAuthorities) {
|
||||||
|
this.contextRunner.withPropertyValues(properties).run((context) -> {
|
||||||
|
ReactiveJwtAuthenticationConverter converter = context.getBean(ReactiveJwtAuthenticationConverter.class);
|
||||||
|
AbstractAuthenticationToken token = converter.convert(jwt).block();
|
||||||
|
assertThat(token).isNotNull().extracting(AbstractAuthenticationToken::getName).isEqualTo(expectedPrincipal);
|
||||||
|
assertThat(token.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(expectedAuthorities);
|
||||||
|
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
|
||||||
|
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() {
|
||||||
|
String propertiesPrincipalClaim = "principal_from_properties";
|
||||||
|
String propertiesPrincipalValue = "from_props";
|
||||||
|
String userConfigPrincipalValue = "from_user_config";
|
||||||
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
|
||||||
|
"spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + propertiesPrincipalClaim)
|
||||||
|
.withUserConfiguration(CustomJwtConverterConfig.class)
|
||||||
|
.run((context) -> {
|
||||||
|
ReactiveJwtAuthenticationConverter converter = context
|
||||||
|
.getBean(ReactiveJwtAuthenticationConverter.class);
|
||||||
|
Jwt jwt = jwt().claim(propertiesPrincipalClaim, propertiesPrincipalValue)
|
||||||
|
.claim(CustomJwtConverterConfig.PRINCIPAL_CLAIM, userConfigPrincipalValue)
|
||||||
|
.build();
|
||||||
|
AbstractAuthenticationToken token = converter.convert(jwt).block();
|
||||||
|
assertThat(token).isNotNull()
|
||||||
|
.extracting(AbstractAuthenticationToken::getName)
|
||||||
|
.isEqualTo(userConfigPrincipalValue)
|
||||||
|
.isNotEqualTo(propertiesPrincipalValue);
|
||||||
|
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
|
||||||
|
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) {
|
private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) {
|
||||||
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
|
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
|
||||||
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||||
|
@ -807,4 +878,18 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomJwtConverterConfig {
|
||||||
|
|
||||||
|
static String PRINCIPAL_CLAIM = "principal_from_user_configuration";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
ReactiveJwtAuthenticationConverter customReactiveJwtAuthenticationConverter() {
|
||||||
|
ReactiveJwtAuthenticationConverter converter = new ReactiveJwtAuthenticationConverter();
|
||||||
|
converter.setPrincipalClaimName(PRINCIPAL_CLAIM);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,12 @@ import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
import org.assertj.core.api.ThrowingConsumer;
|
import org.assertj.core.api.ThrowingConsumer;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||||
import org.mockito.InOrder;
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
import org.springframework.boot.autoconfigure.security.oauth2.resource.JwtConverterCustomizationsArgumentsProvider;
|
||||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||||
|
@ -51,9 +54,11 @@ import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||||
import org.springframework.security.config.BeanIds;
|
import org.springframework.security.config.BeanIds;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
@ -64,6 +69,7 @@ import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
||||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||||
|
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.JwtAuthenticationProvider;
|
||||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||||
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
|
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
|
||||||
|
@ -84,6 +90,7 @@ import static org.mockito.Mockito.mock;
|
||||||
* @author HaiTao Zhang
|
* @author HaiTao Zhang
|
||||||
* @author Mushtaq Ahmed
|
* @author Mushtaq Ahmed
|
||||||
* @author Roman Golovin
|
* @author Roman Golovin
|
||||||
|
* @author Yan Kardziyaka
|
||||||
*/
|
*/
|
||||||
class OAuth2ResourceServerAutoConfigurationTests {
|
class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
|
|
||||||
|
@ -640,6 +647,68 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
.run((context) -> assertThat(context).hasSingleBean(SecurityFilterChain.class));
|
.run((context) -> assertThat(context).hasSingleBean(SecurityFilterChain.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0}")
|
||||||
|
@ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class)
|
||||||
|
void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt,
|
||||||
|
String expectedPrincipal, String[] expectedAuthorities) {
|
||||||
|
this.contextRunner.withPropertyValues(properties).run((context) -> {
|
||||||
|
JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class);
|
||||||
|
AbstractAuthenticationToken token = converter.convert(jwt);
|
||||||
|
assertThat(token).isNotNull().extracting(AbstractAuthenticationToken::getName).isEqualTo(expectedPrincipal);
|
||||||
|
assertThat(token.getAuthorities()).extracting(GrantedAuthority::getAuthority)
|
||||||
|
.containsExactlyInAnyOrder(expectedAuthorities);
|
||||||
|
assertThat(context).hasSingleBean(JwtDecoder.class);
|
||||||
|
assertThat(getBearerTokenFilter(context)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotConfigureJwtConverterIfNoPropertiesAreSet() {
|
||||||
|
this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(JwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfPrincipalClaimNameIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.principal-claim-name=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfAuthorityPrefixIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authority-prefix=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldConfigureJwtConverterIfAuthorityClaimsNameIsSet() {
|
||||||
|
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-claim-name=dummy")
|
||||||
|
.run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() {
|
||||||
|
String propertiesPrincipalClaim = "principal_from_properties";
|
||||||
|
String propertiesPrincipalValue = "from_props";
|
||||||
|
String userConfigPrincipalValue = "from_user_config";
|
||||||
|
this.contextRunner
|
||||||
|
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
|
||||||
|
"spring.security.oauth2.resourceserver.jwt.principal-claim-name=" + propertiesPrincipalClaim)
|
||||||
|
.withUserConfiguration(CustomJwtConverterConfig.class)
|
||||||
|
.run((context) -> {
|
||||||
|
JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class);
|
||||||
|
Jwt jwt = jwt().claim(propertiesPrincipalClaim, propertiesPrincipalValue)
|
||||||
|
.claim(CustomJwtConverterConfig.PRINCIPAL_CLAIM, userConfigPrincipalValue)
|
||||||
|
.build();
|
||||||
|
AbstractAuthenticationToken token = converter.convert(jwt);
|
||||||
|
assertThat(token).isNotNull()
|
||||||
|
.extracting(AbstractAuthenticationToken::getName)
|
||||||
|
.isEqualTo(userConfigPrincipalValue)
|
||||||
|
.isNotEqualTo(propertiesPrincipalValue);
|
||||||
|
assertThat(context).hasSingleBean(JwtDecoder.class);
|
||||||
|
assertThat(getBearerTokenFilter(context)).isNotNull();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
|
private Filter getBearerTokenFilter(AssertableWebApplicationContext context) {
|
||||||
FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
FilterChainProxy filterChain = (FilterChainProxy) context.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
|
||||||
List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
|
List<SecurityFilterChain> filterChains = filterChain.getFilterChains();
|
||||||
|
@ -795,4 +864,18 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
static class CustomJwtConverterConfig {
|
||||||
|
|
||||||
|
static String PRINCIPAL_CLAIM = "principal_from_user_configuration";
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
JwtAuthenticationConverter customJwtAuthenticationConverter() {
|
||||||
|
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||||
|
converter.setPrincipalClaimName(PRINCIPAL_CLAIM);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue