Browse Source

Allow a JwtTypeValidator bean to override Security's default

A change in Spring Security [1] means that type validation is now
performed by default by Spring Security. A breaking side-effect of
this is that setting validateTypes to false no longer has an effect
and the default JwtTypeValidator is still present. Its presence,
wrapped in a DelegatingOAuth2TokenValidator, prevents a user's
JwtTypeValidator bean from being used for type validation.

This commit updates Boot's auto-configuration to change how the
type validators are created. We avoid wrapping in a
DelegatingOAuth2TokenValidator so that the user's custom
JwtTypeValidator can be detected and used in place of the default.
This requires us to create the JwtIssuerValidator rather than using
the createDefaultWithIssuer method as it does not allow additional
validators to be provided.

Fixes gh-48301

[1] 6d3b54df21
pull/48321/head
Andy Wilkinson 3 weeks ago
parent
commit
c3e51d32b8
  1. 28
      module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java
  2. 28
      module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerJwtConfiguration.java
  3. 68
      module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java
  4. 67
      module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java

28
module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java

@ -41,12 +41,12 @@ import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec; import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec;
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.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtValidators; import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder;
@ -99,9 +99,13 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = builder.build(); NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = builder.build();
String issuerUri = this.properties.getIssuerUri(); String issuerUri = this.properties.getIssuerUri();
OAuth2TokenValidator<Jwt> defaultValidator = (issuerUri != null) List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
? JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators.createDefault(); if (issuerUri != null) {
nimbusReactiveJwtDecoder.setJwtValidator(getValidators(defaultValidator)); validators.add(new JwtIssuerValidator(issuerUri));
}
validators.addAll(getValidators());
nimbusReactiveJwtDecoder.setJwtValidator(validators.isEmpty() ? JwtValidators.createDefault()
: JwtValidators.createDefaultWithValidators(validators));
return nimbusReactiveJwtDecoder; return nimbusReactiveJwtDecoder;
} }
@ -111,18 +115,17 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
} }
} }
private OAuth2TokenValidator<Jwt> getValidators(OAuth2TokenValidator<Jwt> defaultValidator) { private List<OAuth2TokenValidator<Jwt>> getValidators() {
List<String> audiences = this.properties.getAudiences(); List<String> audiences = this.properties.getAudiences();
if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) { if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) {
return defaultValidator; return Collections.emptyList();
} }
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
validators.add(defaultValidator);
if (!CollectionUtils.isEmpty(audiences)) { if (!CollectionUtils.isEmpty(audiences)) {
validators.add(audValidator(audiences)); validators.add(audValidator(audiences));
} }
validators.addAll(this.additionalValidators); validators.addAll(this.additionalValidators);
return new DelegatingOAuth2TokenValidator<>(validators); return validators;
} }
private JwtClaimValidator<List<String>> audValidator(List<String> audiences) { private JwtClaimValidator<List<String>> audValidator(List<String> audiences) {
@ -141,7 +144,9 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(publicKey) NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(publicKey)
.signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())) .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm()))
.build(); .build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefault())); List<OAuth2TokenValidator<Jwt>> validators = getValidators();
jwtDecoder.setJwtValidator(validators.isEmpty() ? JwtValidators.createDefault()
: JwtValidators.createDefaultWithValidators(validators));
return jwtDecoder; return jwtDecoder;
} }
@ -171,7 +176,10 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri); JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder.withIssuerLocation(issuerUri);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
NimbusReactiveJwtDecoder jwtDecoder = builder.build(); NimbusReactiveJwtDecoder jwtDecoder = builder.build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefaultWithIssuer(issuerUri))); List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
validators.add(new JwtIssuerValidator(issuerUri));
validators.addAll(getValidators());
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(validators));
return jwtDecoder; return jwtDecoder;
}); });
} }

28
module/spring-boot-security-oauth2-resource-server/src/main/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerJwtConfiguration.java

@ -41,13 +41,13 @@ 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;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames; import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtValidators; 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;
@ -97,9 +97,13 @@ class OAuth2ResourceServerJwtConfiguration {
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
NimbusJwtDecoder nimbusJwtDecoder = builder.build(); NimbusJwtDecoder nimbusJwtDecoder = builder.build();
String issuerUri = this.properties.getIssuerUri(); String issuerUri = this.properties.getIssuerUri();
OAuth2TokenValidator<Jwt> defaultValidator = (issuerUri != null) List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
? JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators.createDefault(); if (issuerUri != null) {
nimbusJwtDecoder.setJwtValidator(getValidators(defaultValidator)); validators.add(new JwtIssuerValidator(issuerUri));
}
validators.addAll(getValidators());
nimbusJwtDecoder.setJwtValidator(validators.isEmpty() ? JwtValidators.createDefault()
: JwtValidators.createDefaultWithValidators(validators));
return nimbusJwtDecoder; return nimbusJwtDecoder;
} }
@ -109,18 +113,17 @@ class OAuth2ResourceServerJwtConfiguration {
} }
} }
private OAuth2TokenValidator<Jwt> getValidators(OAuth2TokenValidator<Jwt> defaultValidator) { private List<OAuth2TokenValidator<Jwt>> getValidators() {
List<String> audiences = this.properties.getAudiences(); List<String> audiences = this.properties.getAudiences();
if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) { if (CollectionUtils.isEmpty(audiences) && this.additionalValidators.isEmpty()) {
return defaultValidator; return Collections.emptyList();
} }
List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>(); List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
validators.add(defaultValidator);
if (!CollectionUtils.isEmpty(audiences)) { if (!CollectionUtils.isEmpty(audiences)) {
validators.add(audValidator(audiences)); validators.add(audValidator(audiences));
} }
validators.addAll(this.additionalValidators); validators.addAll(this.additionalValidators);
return new DelegatingOAuth2TokenValidator<>(validators); return validators;
} }
private JwtClaimValidator<List<String>> audValidator(List<String> audiences) { private JwtClaimValidator<List<String>> audValidator(List<String> audiences) {
@ -139,7 +142,9 @@ class OAuth2ResourceServerJwtConfiguration {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey) NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey)
.signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())) .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm()))
.build(); .build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefault())); List<OAuth2TokenValidator<Jwt>> validators = getValidators();
jwtDecoder.setJwtValidator(validators.isEmpty() ? JwtValidators.createDefault()
: JwtValidators.createDefaultWithValidators(validators));
return jwtDecoder; return jwtDecoder;
} }
@ -168,7 +173,10 @@ class OAuth2ResourceServerJwtConfiguration {
JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withIssuerLocation(issuerUri); JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withIssuerLocation(issuerUri);
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
NimbusJwtDecoder jwtDecoder = builder.build(); NimbusJwtDecoder jwtDecoder = builder.build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators.createDefaultWithIssuer(issuerUri))); List<OAuth2TokenValidator<Jwt>> validators = new ArrayList<>();
validators.add(new JwtIssuerValidator(issuerUri));
validators.addAll(getValidators());
jwtDecoder.setJwtValidator(JwtValidators.createDefaultWithValidators(validators));
return jwtDecoder; return jwtDecoder;
}); });
} }

68
module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java

@ -73,9 +73,11 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
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.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
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;
@ -727,6 +729,60 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.doesNotHaveBean(ReactiveManagementWebSecurityAutoConfiguration.class)); .doesNotHaveBean(ReactiveManagementWebSecurityAutoConfiguration.class));
} }
@Test
@SuppressWarnings("unchecked")
void customTypeValidatorCanReplaceDefaultWhenUsingIssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
String path = "test";
String issuer = this.server.url(path).toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri)
.withUserConfiguration(CustomJwtTypeValidatorConfig.class)
.run((context) -> {
SupplierReactiveJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierReactiveJwtDecoder.class);
Mono<ReactiveJwtDecoder> jwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
.getField(supplierJwtDecoderBean, "jwtDecoderMono");
assertThat(jwtDecoderSupplier).isNotNull();
ReactiveJwtDecoder jwtDecoder = jwtDecoderSupplier.block();
assertThat(jwtDecoder).isNotNull();
assertThat(context).hasBean("customJwtTypeValidator");
OAuth2TokenValidator<Jwt> customValidator = (OAuth2TokenValidator<Jwt>) context
.getBean("customJwtTypeValidator");
validate(jwt().claim("iss", URI.create(issuerUri).toURL()).header(JoseHeaderNames.TYP, "custom-type"),
jwtDecoder,
(validators) -> assertThat(validators).contains(customValidator)
.satisfiesOnlyOnce(
(validator) -> assertThat(validator).isInstanceOf(JwtTypeValidator.class)));
});
}
@Test
@SuppressWarnings("unchecked")
void customTypeValidatorCanReplaceDefaultWhenUsingJwkSetUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
String path = "test";
String issuer = this.server.url(path).toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.withUserConfiguration(CustomJwtTypeValidatorConfig.class)
.run((context) -> {
ReactiveJwtDecoder jwtDecoder = context.getBean(ReactiveJwtDecoder.class);
assertThat(context).hasBean("customJwtTypeValidator");
OAuth2TokenValidator<Jwt> customValidator = (OAuth2TokenValidator<Jwt>) context
.getBean("customJwtTypeValidator");
validate(jwt().header(JoseHeaderNames.TYP, "custom-type"), jwtDecoder,
(validators) -> assertThat(validators).contains(customValidator)
.satisfiesOnlyOnce(
(validator) -> assertThat(validator).isInstanceOf(JwtTypeValidator.class)));
});
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) { private void assertFilterConfiguredWithJwtAuthenticationManager(AssertableReactiveWebApplicationContext context) {
MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context MatcherSecurityWebFilterChain filterChain = (MatcherSecurityWebFilterChain) context
@ -826,7 +882,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
.getField(jwtDecoder, "jwtValidator"); .getField(jwtDecoder, "jwtValidator");
assertThat(jwtValidator).isNotNull(); assertThat(jwtValidator).isNotNull();
assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse(); assertThat(jwtValidator.validate(builder.build()).getErrors()).isEmpty();
validatorsConsumer.accept(extractValidators(jwtValidator)); validatorsConsumer.accept(extractValidators(jwtValidator));
} }
@ -934,6 +990,16 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class CustomJwtTypeValidatorConfig {
@Bean
JwtTypeValidator customJwtTypeValidator() {
return new JwtTypeValidator("custom-type");
}
}
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@WithResource(name = "public-key-location", content = """ @WithResource(name = "public-key-location", content = """

67
module/spring-boot-security-oauth2-resource-server/src/test/java/org/springframework/boot/security/oauth2/server/resource/autoconfigure/servlet/OAuth2ResourceServerAutoConfigurationTests.java

@ -70,10 +70,12 @@ import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.FactorGrantedAuthority; import org.springframework.security.core.authority.FactorGrantedAuthority;
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.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimValidator; import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTypeValidator;
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;
@ -745,6 +747,59 @@ class OAuth2ResourceServerAutoConfigurationTests {
.doesNotHaveBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN)); .doesNotHaveBean(MANAGEMENT_SECURITY_FILTER_CHAIN_BEAN));
} }
@Test
@SuppressWarnings("unchecked")
void customTypeValidatorCanReplaceDefaultWhenUsingIssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
String path = "test";
String issuer = this.server.url(path).toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
String issuerUri = "http://" + this.server.getHostName() + ":" + this.server.getPort() + "/" + path;
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=" + issuerUri)
.withUserConfiguration(CustomJwtTypeValidatorConfig.class)
.run((context) -> {
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
.getField(supplierJwtDecoderBean, "delegate");
assertThat(jwtDecoderSupplier).isNotNull();
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
assertThat(context).hasBean("customJwtTypeValidator");
OAuth2TokenValidator<Jwt> customValidator = (OAuth2TokenValidator<Jwt>) context
.getBean("customJwtTypeValidator");
validate(jwt().claim("iss", URI.create(issuerUri).toURL()).header(JoseHeaderNames.TYP, "custom-type"),
jwtDecoder,
(validators) -> assertThat(validators).contains(customValidator)
.satisfiesOnlyOnce(
(validator) -> assertThat(validator).isInstanceOf(JwtTypeValidator.class)));
});
}
@Test
@SuppressWarnings("unchecked")
void customTypeValidatorCanReplaceDefaultWhenUsingJwkSetUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
String path = "test";
String issuer = this.server.url(path).toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com")
.withUserConfiguration(CustomJwtTypeValidatorConfig.class)
.run((context) -> {
JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
assertThat(context).hasBean("customJwtTypeValidator");
OAuth2TokenValidator<Jwt> customValidator = (OAuth2TokenValidator<Jwt>) context
.getBean("customJwtTypeValidator");
validate(jwt().header(JoseHeaderNames.TYP, "custom-type"), jwtDecoder,
(validators) -> assertThat(validators).contains(customValidator)
.satisfiesOnlyOnce(
(validator) -> assertThat(validator).isInstanceOf(JwtTypeValidator.class)));
});
}
private @Nullable Filter getBearerTokenFilter(AssertableWebApplicationContext context) { private @Nullable 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();
@ -814,7 +869,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils DelegatingOAuth2TokenValidator<Jwt> jwtValidator = (DelegatingOAuth2TokenValidator<Jwt>) ReflectionTestUtils
.getField(jwtDecoder, "jwtValidator"); .getField(jwtDecoder, "jwtValidator");
assertThat(jwtValidator).isNotNull(); assertThat(jwtValidator).isNotNull();
assertThat(jwtValidator.validate(builder.build()).hasErrors()).isFalse(); assertThat(jwtValidator.validate(builder.build()).getErrors()).isEmpty();
validatorsConsumer.accept(extractValidators(jwtValidator)); validatorsConsumer.accept(extractValidators(jwtValidator));
} }
@ -904,6 +959,16 @@ class OAuth2ResourceServerAutoConfigurationTests {
} }
@Configuration(proxyBeanMethods = false)
static class CustomJwtTypeValidatorConfig {
@Bean
JwtTypeValidator customJwtTypeValidator() {
return new JwtTypeValidator("custom-type");
}
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
static class CustomJwtConverterConfig { static class CustomJwtConverterConfig {

Loading…
Cancel
Save