mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-23 01:39:02 +00:00
Auto-configure ExpressionJwtGrantedAuthoritiesConverters
Closes gh-48490
This commit is contained in:
+79
-7
@@ -16,17 +16,31 @@
|
||||
|
||||
package org.springframework.boot.security.oauth2.server.resource.autoconfigure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.OnPropertyListCondition;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DelegatingJwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ExpressionJwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for JWT converter beans.
|
||||
@@ -47,18 +61,24 @@ class JwtConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
JwtAuthenticationConverter jwtAuthenticationConverter(OAuth2ResourceServerProperties properties) {
|
||||
return jwtAuthenticationConverter(properties.getJwt());
|
||||
}
|
||||
|
||||
private JwtAuthenticationConverter jwtAuthenticationConverter(OAuth2ResourceServerProperties.Jwt properties) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
OAuth2ResourceServerProperties.Jwt jwtProperties = properties.getJwt();
|
||||
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
|
||||
map.from(properties::getPrincipalClaimName).to(converter::setPrincipalClaimName);
|
||||
map.from(properties).as(this::getGrantedAuthoritiesConverter).to(converter::setJwtGrantedAuthoritiesConverter);
|
||||
map.from(jwtProperties::getPrincipalClaimName).to(converter::setPrincipalClaimName);
|
||||
map.from(jwtProperties).as(this::grantedAuthoritiesConverter).to(converter::setJwtGrantedAuthoritiesConverter);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private JwtGrantedAuthoritiesConverter getGrantedAuthoritiesConverter(
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter(
|
||||
OAuth2ResourceServerProperties.Jwt properties) {
|
||||
List<String> authoritiesExpressions = properties.getAuthoritiesExpressions();
|
||||
if (CollectionUtils.isEmpty(authoritiesExpressions)) {
|
||||
return createJwtGrantedAuthoritiesConverter(properties);
|
||||
}
|
||||
return createExpressionJwtGrantedAuthoritiesConverters(properties, authoritiesExpressions);
|
||||
}
|
||||
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> createJwtGrantedAuthoritiesConverter(
|
||||
OAuth2ResourceServerProperties.Jwt properties) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
|
||||
@@ -68,6 +88,44 @@ class JwtConverterConfiguration {
|
||||
return converter;
|
||||
}
|
||||
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> createExpressionJwtGrantedAuthoritiesConverters(
|
||||
OAuth2ResourceServerProperties.Jwt properties, List<String> authoritiesExpressions) {
|
||||
checkMutualExclusivity(properties);
|
||||
List<Converter<Jwt, Collection<GrantedAuthority>>> converters = new ArrayList<>();
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
for (String authoritiesExpression : authoritiesExpressions) {
|
||||
ExpressionJwtGrantedAuthoritiesConverter converter = new ExpressionJwtGrantedAuthoritiesConverter(
|
||||
parser.parseExpression(authoritiesExpression));
|
||||
if (properties.getAuthorityPrefix() != null) {
|
||||
converter.setAuthorityPrefix(properties.getAuthorityPrefix());
|
||||
}
|
||||
converters.add(converter);
|
||||
}
|
||||
return (converters.size() == 1) ? converters.get(0) : new DelegatingJwtGrantedAuthoritiesConverter(converters);
|
||||
}
|
||||
|
||||
private void checkMutualExclusivity(OAuth2ResourceServerProperties.Jwt properties) {
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> {
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
properties.getAuthoritiesExpressions());
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-claim-name",
|
||||
properties.getAuthoritiesClaimName());
|
||||
}, (value) -> !nullOrEmptyList(value));
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> {
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
properties.getAuthoritiesExpressions());
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter",
|
||||
properties.getAuthoritiesClaimDelimiter());
|
||||
}, (value) -> !nullOrEmptyList(value));
|
||||
}
|
||||
|
||||
private boolean nullOrEmptyList(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return (value instanceof List<?> list) ? list.isEmpty() : false;
|
||||
}
|
||||
|
||||
static class PropertiesCondition extends AnyNestedCondition {
|
||||
|
||||
PropertiesCondition() {
|
||||
@@ -89,6 +147,20 @@ class JwtConverterConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Conditional(OnAuthoritiesExpressionsCondition.class)
|
||||
static class OnAuthoritiesExpressions {
|
||||
|
||||
}
|
||||
|
||||
static class OnAuthoritiesExpressionsCondition extends OnPropertyListCondition {
|
||||
|
||||
OnAuthoritiesExpressionsCondition() {
|
||||
super("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
() -> ConditionMessage.forCondition("Authorities expressions"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -98,6 +98,14 @@ public class OAuth2ResourceServerProperties {
|
||||
*/
|
||||
private @Nullable String authoritiesClaimName;
|
||||
|
||||
/**
|
||||
* List of expressions to use to extract authorities from a JWT. Mutually
|
||||
* exclusive with
|
||||
* 'spring.security.oauth2.resourceserver.jwt.authorities-claim-name' and
|
||||
* 'spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter'.
|
||||
*/
|
||||
private List<String> authoritiesExpressions = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* JWT principal claim name.
|
||||
*/
|
||||
@@ -167,6 +175,14 @@ public class OAuth2ResourceServerProperties {
|
||||
this.authoritiesClaimName = authoritiesClaimName;
|
||||
}
|
||||
|
||||
public List<String> getAuthoritiesExpressions() {
|
||||
return this.authoritiesExpressions;
|
||||
}
|
||||
|
||||
public void setAuthoritiesExpressions(List<String> authoritiesExpressions) {
|
||||
this.authoritiesExpressions = authoritiesExpressions;
|
||||
}
|
||||
|
||||
public @Nullable String getPrincipalClaimName() {
|
||||
return this.principalClaimName;
|
||||
}
|
||||
|
||||
+91
-13
@@ -16,19 +16,33 @@
|
||||
|
||||
package org.springframework.boot.security.oauth2.server.resource.autoconfigure.reactive;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.OnPropertyListCondition;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.OAuth2ResourceServerProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.DelegatingJwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ExpressionJwtGrantedAuthoritiesConverter;
|
||||
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.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* {@link Configuration @Configuration} for JWT converter beans.
|
||||
@@ -50,22 +64,72 @@ class ReactiveJwtConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactiveJwtAuthenticationConverter reactiveJwtAuthenticationConverter(OAuth2ResourceServerProperties properties) {
|
||||
return reactiveJwtAuthenticationConverter(properties.getJwt());
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
OAuth2ResourceServerProperties.Jwt jwtProperties = properties.getJwt();
|
||||
ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
|
||||
map.from(jwtProperties::getPrincipalClaimName).to(jwtAuthenticationConverter::setPrincipalClaimName);
|
||||
map.from(jwtProperties)
|
||||
.as(this::grantedAuthoritiesConverter)
|
||||
.as(ReactiveJwtGrantedAuthoritiesConverterAdapter::new)
|
||||
.to(jwtAuthenticationConverter::setJwtGrantedAuthoritiesConverter);
|
||||
return jwtAuthenticationConverter;
|
||||
}
|
||||
|
||||
private ReactiveJwtAuthenticationConverter reactiveJwtAuthenticationConverter(
|
||||
OAuth2ResourceServerProperties.Jwt properties1) {
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> grantedAuthoritiesConverter(
|
||||
OAuth2ResourceServerProperties.Jwt properties) {
|
||||
List<String> authoritiesExpressions = properties.getAuthoritiesExpressions();
|
||||
if (CollectionUtils.isEmpty(authoritiesExpressions)) {
|
||||
return createJwtGrantedAuthoritiesConverter(properties);
|
||||
}
|
||||
return createExpressionJwtGrantedAuthoritiesConverters(properties, authoritiesExpressions);
|
||||
}
|
||||
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> createJwtGrantedAuthoritiesConverter(
|
||||
OAuth2ResourceServerProperties.Jwt properties) {
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
|
||||
map.from(properties1.getAuthorityPrefix()).to(grantedAuthoritiesConverter::setAuthorityPrefix);
|
||||
map.from(properties1.getAuthoritiesClaimDelimiter())
|
||||
.to(grantedAuthoritiesConverter::setAuthoritiesClaimDelimiter);
|
||||
map.from(properties1.getAuthoritiesClaimName()).to(grantedAuthoritiesConverter::setAuthoritiesClaimName);
|
||||
ReactiveJwtAuthenticationConverter jwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter();
|
||||
map.from(properties1.getPrincipalClaimName()).to(jwtAuthenticationConverter::setPrincipalClaimName);
|
||||
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(
|
||||
new ReactiveJwtGrantedAuthoritiesConverterAdapter(grantedAuthoritiesConverter));
|
||||
return jwtAuthenticationConverter;
|
||||
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
|
||||
map.from(properties::getAuthorityPrefix).to(converter::setAuthorityPrefix);
|
||||
map.from(properties::getAuthoritiesClaimDelimiter).to(converter::setAuthoritiesClaimDelimiter);
|
||||
map.from(properties::getAuthoritiesClaimName).to(converter::setAuthoritiesClaimName);
|
||||
return converter;
|
||||
}
|
||||
|
||||
private Converter<Jwt, Collection<GrantedAuthority>> createExpressionJwtGrantedAuthoritiesConverters(
|
||||
OAuth2ResourceServerProperties.Jwt properties, List<String> authoritiesExpressions) {
|
||||
checkMutualExclusivity(properties);
|
||||
List<Converter<Jwt, Collection<GrantedAuthority>>> converters = new ArrayList<>();
|
||||
SpelExpressionParser parser = new SpelExpressionParser();
|
||||
for (String authoritiesExpression : authoritiesExpressions) {
|
||||
ExpressionJwtGrantedAuthoritiesConverter converter = new ExpressionJwtGrantedAuthoritiesConverter(
|
||||
parser.parseExpression(authoritiesExpression));
|
||||
if (properties.getAuthorityPrefix() != null) {
|
||||
converter.setAuthorityPrefix(properties.getAuthorityPrefix());
|
||||
}
|
||||
converters.add(converter);
|
||||
}
|
||||
return (converters.size() == 1) ? converters.get(0) : new DelegatingJwtGrantedAuthoritiesConverter(converters);
|
||||
}
|
||||
|
||||
private void checkMutualExclusivity(OAuth2ResourceServerProperties.Jwt properties) {
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> {
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
properties.getAuthoritiesExpressions());
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-claim-name",
|
||||
properties.getAuthoritiesClaimName());
|
||||
}, (value) -> !nullOrEmptyList(value));
|
||||
MutuallyExclusiveConfigurationPropertiesException.throwIfMultipleMatchingValuesIn((entries) -> {
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
properties.getAuthoritiesExpressions());
|
||||
entries.put("spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter",
|
||||
properties.getAuthoritiesClaimDelimiter());
|
||||
}, (value) -> !nullOrEmptyList(value));
|
||||
}
|
||||
|
||||
private boolean nullOrEmptyList(Object value) {
|
||||
if (value == null) {
|
||||
return true;
|
||||
}
|
||||
return (value instanceof List<?> list) ? list.isEmpty() : false;
|
||||
}
|
||||
|
||||
static class PropertiesCondition extends AnyNestedCondition {
|
||||
@@ -89,6 +153,20 @@ class ReactiveJwtConverterConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Conditional(OnAuthoritiesExpressionsCondition.class)
|
||||
static class OnAuthoritiesExpressions {
|
||||
|
||||
}
|
||||
|
||||
static class OnAuthoritiesExpressionsCondition extends OnPropertyListCondition {
|
||||
|
||||
OnAuthoritiesExpressionsCondition() {
|
||||
super("spring.security.oauth2.resourceserver.jwt.authorities-expressions",
|
||||
() -> ConditionMessage.forCondition("Authorities expressions"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+68
@@ -47,6 +47,7 @@ import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
@@ -74,6 +75,7 @@ import org.springframework.security.oauth2.jwt.JwtTypeValidator;
|
||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ExpressionJwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
@@ -689,6 +691,72 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).hasSingleBean(JwtAuthenticationConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureJwtConverterIfAuthoritiesExpressionIsSet() {
|
||||
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions=zero")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JwtAuthenticationConverter.class);
|
||||
JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class);
|
||||
assertThat(converter).extracting("jwtGrantedAuthoritiesConverter")
|
||||
.isInstanceOf(ExpressionJwtGrantedAuthoritiesConverter.class)
|
||||
.extracting("authorityPrefix")
|
||||
.isEqualTo("SCOPE_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureJwtConverterIfAuthoritiesExpressionsAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-expressions[1]=one")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JwtAuthenticationConverter.class);
|
||||
JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class);
|
||||
assertThat(converter)
|
||||
.extracting("jwtGrantedAuthoritiesConverter.authoritiesConverters", InstanceOfAssertFactories.LIST)
|
||||
.hasSize(2)
|
||||
.extracting("authorityPrefix")
|
||||
.containsOnly("SCOPE_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyCustomAuthorityPrefixIfAuthoritiesExpressionsAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-expressions[1]=one",
|
||||
"spring.security.oauth2.resourceserver.jwt.authority-prefix=CUSTOM_")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(JwtAuthenticationConverter.class);
|
||||
JwtAuthenticationConverter converter = context.getBean(JwtAuthenticationConverter.class);
|
||||
assertThat(converter)
|
||||
.extracting("jwtGrantedAuthoritiesConverter.authoritiesConverters", InstanceOfAssertFactories.LIST)
|
||||
.hasSize(2)
|
||||
.extracting("authorityPrefix")
|
||||
.containsOnly("CUSTOM_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfBothAuthoritiesExpressionsAndAuthoritiesClaimDelimiterAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter=delimiter")
|
||||
.run((context) -> assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfBothAuthoritiesExpressionsAndAuthoritiesClaimNameAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-claim-name=name")
|
||||
.run((context) -> assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jwtAuthenticationConverterByJwtConfigIsConditionalOnMissingBean() {
|
||||
String propertiesPrincipalClaim = "principal_from_properties";
|
||||
|
||||
+73
@@ -48,6 +48,7 @@ import reactor.core.publisher.Mono;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.context.properties.source.MutuallyExclusiveConfigurationPropertiesException;
|
||||
import org.springframework.boot.security.oauth2.server.resource.autoconfigure.JwtConverterCustomizationsArgumentsProvider;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
|
||||
@@ -77,6 +78,7 @@ import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ExpressionJwtGrantedAuthoritiesConverter;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
@@ -678,6 +680,77 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||
.run((context) -> assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureJwtConverterIfAuthoritiesExpressionIsSet() {
|
||||
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions=zero")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class);
|
||||
ReactiveJwtAuthenticationConverter converter = context
|
||||
.getBean(ReactiveJwtAuthenticationConverter.class);
|
||||
assertThat(converter).extracting("jwtGrantedAuthoritiesConverter.grantedAuthoritiesConverter")
|
||||
.isInstanceOf(ExpressionJwtGrantedAuthoritiesConverter.class)
|
||||
.extracting("authorityPrefix")
|
||||
.isEqualTo("SCOPE_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureJwtConverterIfAuthoritiesExpressionsAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-expressions[1]=one")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class);
|
||||
ReactiveJwtAuthenticationConverter converter = context
|
||||
.getBean(ReactiveJwtAuthenticationConverter.class);
|
||||
assertThat(converter)
|
||||
.extracting("jwtGrantedAuthoritiesConverter.grantedAuthoritiesConverter.authoritiesConverters",
|
||||
InstanceOfAssertFactories.LIST)
|
||||
.hasSize(2)
|
||||
.extracting("authorityPrefix")
|
||||
.containsOnly("SCOPE_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldApplyCustomAuthorityPrefixIfAuthoritiesExpressionsAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-expressions[1]=one",
|
||||
"spring.security.oauth2.resourceserver.jwt.authority-prefix=CUSTOM_")
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(ReactiveJwtAuthenticationConverter.class);
|
||||
ReactiveJwtAuthenticationConverter converter = context
|
||||
.getBean(ReactiveJwtAuthenticationConverter.class);
|
||||
assertThat(converter)
|
||||
.extracting("jwtGrantedAuthoritiesConverter.grantedAuthoritiesConverter.authoritiesConverters",
|
||||
InstanceOfAssertFactories.LIST)
|
||||
.hasSize(2)
|
||||
.extracting("authorityPrefix")
|
||||
.containsOnly("CUSTOM_");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfBothAuthoritiesExpressionsAndAuthoritiesClaimDelimiterAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-claim-delimiter=delimiter")
|
||||
.run((context) -> assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfBothAuthoritiesExpressionsAndAuthoritiesClaimNameAreSet() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.authorities-expressions[0]=zero",
|
||||
"spring.security.oauth2.resourceserver.jwt.authorities-claim-name=name")
|
||||
.run((context) -> assertThat(context).getFailure()
|
||||
.rootCause()
|
||||
.isInstanceOf(MutuallyExclusiveConfigurationPropertiesException.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@ArgumentsSource(JwtConverterCustomizationsArgumentsProvider.class)
|
||||
void autoConfigurationShouldConfigureResourceServerWithJwtConverterCustomizations(String[] properties, Jwt jwt,
|
||||
|
||||
Reference in New Issue
Block a user