Enable customization of JWK Set URI decoder builders
Closes gh-20750
This commit is contained in:
parent
45068c777f
commit
a03fe8befc
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.reactive;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder.JwkSetUriReactiveJwtDecoderBuilder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface for the customization of the
|
||||||
|
* {@link JwkSetUriReactiveJwtDecoderBuilder} used to create the auto-configured
|
||||||
|
* {@link ReactiveJwtDecoder} for a JWK set URI that has been configured directly or
|
||||||
|
* obtained through an issuer URI.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @since 3.1.0
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface JwkSetUriReactiveJwtDecoderBuilderCustomizer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize the given {@code builder}.
|
||||||
|
* @param builder the {@code builder} to customize
|
||||||
|
*/
|
||||||
|
void customize(JwkSetUriReactiveJwtDecoderBuilder builder);
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
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;
|
||||||
|
@ -45,8 +46,8 @@ 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.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.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
|
|
||||||
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
|
||||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -77,11 +78,12 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
|
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
|
||||||
ReactiveJwtDecoder jwtDecoder() {
|
ReactiveJwtDecoder jwtDecoder(ObjectProvider<JwkSetUriReactiveJwtDecoderBuilderCustomizer> customizers) {
|
||||||
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = NimbusReactiveJwtDecoder
|
JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder
|
||||||
.withJwkSetUri(this.properties.getJwkSetUri())
|
.withJwkSetUri(this.properties.getJwkSetUri())
|
||||||
.jwsAlgorithms(this::jwsAlgorithms)
|
.jwsAlgorithms(this::jwsAlgorithms);
|
||||||
.build();
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||||
|
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = builder.build();
|
||||||
String issuerUri = this.properties.getIssuerUri();
|
String issuerUri = this.properties.getIssuerUri();
|
||||||
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
|
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
|
||||||
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
|
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
|
||||||
|
@ -138,10 +140,13 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(IssuerUriCondition.class)
|
@Conditional(IssuerUriCondition.class)
|
||||||
SupplierReactiveJwtDecoder jwtDecoderByIssuerUri() {
|
SupplierReactiveJwtDecoder jwtDecoderByIssuerUri(
|
||||||
|
ObjectProvider<JwkSetUriReactiveJwtDecoderBuilderCustomizer> customizers) {
|
||||||
return new SupplierReactiveJwtDecoder(() -> {
|
return new SupplierReactiveJwtDecoder(() -> {
|
||||||
NimbusReactiveJwtDecoder jwtDecoder = (NimbusReactiveJwtDecoder) ReactiveJwtDecoders
|
JwkSetUriReactiveJwtDecoderBuilder builder = NimbusReactiveJwtDecoder
|
||||||
.fromIssuerLocation(this.properties.getIssuerUri());
|
.withIssuerLocation(this.properties.getIssuerUri());
|
||||||
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||||
|
NimbusReactiveJwtDecoder jwtDecoder = builder.build();
|
||||||
jwtDecoder.setJwtValidator(
|
jwtDecoder.setJwtValidator(
|
||||||
getValidators(() -> JwtValidators.createDefaultWithIssuer(this.properties.getIssuerUri())));
|
getValidators(() -> JwtValidators.createDefaultWithIssuer(this.properties.getIssuerUri())));
|
||||||
return jwtDecoder;
|
return jwtDecoder;
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.servlet;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface for the customization of the {@link JwkSetUriJwtDecoderBuilder} used
|
||||||
|
* to create the auto-configured {@link JwtDecoder} for a JWK set URI that has been
|
||||||
|
* configured directly or obtained through an issuer URI.
|
||||||
|
*
|
||||||
|
* @author Andy Wilkinson
|
||||||
|
* @since 3.1.0
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface JwkSetUriJwtDecoderBuilderCustomizer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customize the given {@code builder}.
|
||||||
|
* @param builder the {@code builder} to customize
|
||||||
|
*/
|
||||||
|
void customize(JwkSetUriJwtDecoderBuilder builder);
|
||||||
|
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.ObjectProvider;
|
||||||
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;
|
||||||
|
@ -44,9 +45,9 @@ 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.JwtDecoders;
|
|
||||||
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.SupplierJwtDecoder;
|
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -78,10 +79,11 @@ class OAuth2ResourceServerJwtConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
|
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
|
||||||
JwtDecoder jwtDecoderByJwkKeySetUri() {
|
JwtDecoder jwtDecoderByJwkKeySetUri(ObjectProvider<JwkSetUriJwtDecoderBuilderCustomizer> customizers) {
|
||||||
NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
|
JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
|
||||||
.jwsAlgorithms(this::jwsAlgorithms)
|
.jwsAlgorithms(this::jwsAlgorithms);
|
||||||
.build();
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||||
|
NimbusJwtDecoder nimbusJwtDecoder = builder.build();
|
||||||
String issuerUri = this.properties.getIssuerUri();
|
String issuerUri = this.properties.getIssuerUri();
|
||||||
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
|
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
|
||||||
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
|
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
|
||||||
|
@ -138,10 +140,12 @@ class OAuth2ResourceServerJwtConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(IssuerUriCondition.class)
|
@Conditional(IssuerUriCondition.class)
|
||||||
SupplierJwtDecoder jwtDecoderByIssuerUri() {
|
SupplierJwtDecoder jwtDecoderByIssuerUri(ObjectProvider<JwkSetUriJwtDecoderBuilderCustomizer> customizers) {
|
||||||
return new SupplierJwtDecoder(() -> {
|
return new SupplierJwtDecoder(() -> {
|
||||||
String issuerUri = this.properties.getIssuerUri();
|
String issuerUri = this.properties.getIssuerUri();
|
||||||
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromIssuerLocation(issuerUri);
|
JwkSetUriJwtDecoderBuilder builder = NimbusJwtDecoder.withIssuerLocation(issuerUri);
|
||||||
|
customizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||||
|
NimbusJwtDecoder jwtDecoder = builder.build();
|
||||||
jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri)));
|
jwtDecoder.setJwtValidator(getValidators(() -> JwtValidators.createDefaultWithIssuer(issuerUri)));
|
||||||
return jwtDecoder;
|
return jwtDecoder;
|
||||||
});
|
});
|
||||||
|
|
|
@ -35,6 +35,7 @@ import okhttp3.mockwebserver.MockWebServer;
|
||||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
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.mockito.InOrder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
|
@ -43,6 +44,7 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplic
|
||||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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;
|
||||||
|
@ -72,6 +74,8 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.springframework.web.server.WebFilter;
|
import org.springframework.web.server.WebFilter;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.springframework.security.config.Customizer.withDefaults;
|
import static org.springframework.security.config.Customizer.withDefaults;
|
||||||
|
|
||||||
|
@ -92,7 +96,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
|
|
||||||
private MockWebServer server;
|
private MockWebServer server;
|
||||||
|
|
||||||
private static final Duration TIMEOUT = Duration.ofSeconds(5);
|
private static final Duration TIMEOUT = Duration.ofSeconds(5000000);
|
||||||
|
|
||||||
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\","
|
private static final String JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\","
|
||||||
+ "\"kid\":\"one\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGm"
|
+ "\"kid\":\"one\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGm"
|
||||||
|
@ -127,9 +131,21 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$1.signatureAlgorithms")
|
assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$1.signatureAlgorithms")
|
||||||
.asInstanceOf(InstanceOfAssertFactories.collection(SignatureAlgorithm.class))
|
.asInstanceOf(InstanceOfAssertFactories.collection(SignatureAlgorithm.class))
|
||||||
.containsExactlyInAnyOrder(SignatureAlgorithm.RS512);
|
.containsExactlyInAnyOrder(SignatureAlgorithm.RS512);
|
||||||
|
assertJwkSetUriReactiveJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertJwkSetUriReactiveJwtDecoderBuilderCustomization(
|
||||||
|
AssertableReactiveWebApplicationContext context) {
|
||||||
|
JwkSetUriReactiveJwtDecoderBuilderCustomizer customizer = context.getBean("decoderBuilderCustomizer",
|
||||||
|
JwkSetUriReactiveJwtDecoderBuilderCustomizer.class);
|
||||||
|
JwkSetUriReactiveJwtDecoderBuilderCustomizer anotherCustomizer = context
|
||||||
|
.getBean("anotherDecoderBuilderCustomizer", JwkSetUriReactiveJwtDecoderBuilderCustomizer.class);
|
||||||
|
InOrder inOrder = inOrder(customizer, anotherCustomizer);
|
||||||
|
inOrder.verify(customizer).customize(any());
|
||||||
|
inOrder.verify(anotherCustomizer).customize(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void autoConfigurationUsingJwkSetUriShouldConfigureResourceServerUsingMultipleJwsAlgorithms() {
|
void autoConfigurationUsingJwkSetUriShouldConfigureResourceServerUsingMultipleJwsAlgorithms() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
|
@ -141,6 +157,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
.asInstanceOf(InstanceOfAssertFactories.collection(SignatureAlgorithm.class))
|
.asInstanceOf(InstanceOfAssertFactories.collection(SignatureAlgorithm.class))
|
||||||
.containsExactlyInAnyOrder(SignatureAlgorithm.RS256, SignatureAlgorithm.RS384,
|
.containsExactlyInAnyOrder(SignatureAlgorithm.RS256, SignatureAlgorithm.RS384,
|
||||||
SignatureAlgorithm.RS512);
|
SignatureAlgorithm.RS512);
|
||||||
|
assertJwkSetUriReactiveJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,7 +189,6 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {
|
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {
|
||||||
this.server = new MockWebServer();
|
this.server = new MockWebServer();
|
||||||
this.server.start();
|
this.server.start();
|
||||||
|
@ -187,18 +203,32 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
||||||
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||||
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
||||||
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
|
// Trigger calls to the issuer by decoding a token
|
||||||
.getBean(SupplierReactiveJwtDecoder.class);
|
decodeJwt(context);
|
||||||
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
|
assertJwkSetUriReactiveJwtDecoderBuilderCustomization(context);
|
||||||
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
|
|
||||||
reactiveJwtDecoderSupplier.block(TIMEOUT);
|
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isOne();
|
assertThat(this.server.getRequestCount()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void decodeJwt(AssertableReactiveWebApplicationContext context) {
|
||||||
|
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context.getBean(SupplierReactiveJwtDecoder.class);
|
||||||
|
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
|
||||||
|
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
|
||||||
|
try {
|
||||||
|
reactiveJwtDecoderSupplier.flatMap((decoder) -> decoder.decode("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
|
||||||
|
+ "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0."
|
||||||
|
+ "NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygTqVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9RiwrV7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ"))
|
||||||
|
.block(TIMEOUT);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
// This fails, but it's enough to check that the expected HTTP calls
|
||||||
|
// are made
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
|
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
|
||||||
this.server = new MockWebServer();
|
this.server = new MockWebServer();
|
||||||
this.server.start();
|
this.server.start();
|
||||||
|
@ -212,18 +242,15 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
||||||
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||||
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
||||||
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
|
// Trigger calls to the issuer by decoding a token
|
||||||
.getBean(SupplierReactiveJwtDecoder.class);
|
decodeJwt(context);
|
||||||
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
|
// assertJwkSetUriReactiveJwtDecoderBuilderCustomization(context);
|
||||||
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
|
|
||||||
reactiveJwtDecoderSupplier.block(TIMEOUT);
|
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isEqualTo(2);
|
assertThat(this.server.getRequestCount()).isEqualTo(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
|
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
|
||||||
this.server = new MockWebServer();
|
this.server = new MockWebServer();
|
||||||
this.server.start();
|
this.server.start();
|
||||||
|
@ -237,14 +264,12 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
|
||||||
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
assertFilterConfiguredWithJwtAuthenticationManager(context);
|
||||||
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
|
||||||
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
|
// Trigger calls to the issuer by decoding a token
|
||||||
.getBean(SupplierReactiveJwtDecoder.class);
|
decodeJwt(context);
|
||||||
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
|
assertJwkSetUriReactiveJwtDecoderBuilderCustomization(context);
|
||||||
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
|
|
||||||
reactiveJwtDecoderSupplier.block(TIMEOUT);
|
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isEqualTo(3);
|
assertThat(this.server.getRequestCount()).isEqualTo(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -666,6 +691,18 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
|
||||||
return mock(MapReactiveUserDetailsService.class);
|
return mock(MapReactiveUserDetailsService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(1)
|
||||||
|
JwkSetUriReactiveJwtDecoderBuilderCustomizer decoderBuilderCustomizer() {
|
||||||
|
return mock(JwkSetUriReactiveJwtDecoderBuilderCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(2)
|
||||||
|
JwkSetUriReactiveJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() {
|
||||||
|
return mock(JwkSetUriReactiveJwtDecoderBuilderCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
|
|
@ -35,6 +35,7 @@ import okhttp3.mockwebserver.MockWebServer;
|
||||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||||
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.mockito.InOrder;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||||
|
@ -43,6 +44,7 @@ import org.springframework.boot.test.context.assertj.AssertableWebApplicationCon
|
||||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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;
|
||||||
|
@ -67,6 +69,8 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,9 +109,20 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
.run((context) -> {
|
.run((context) -> {
|
||||||
assertThat(context).hasSingleBean(JwtDecoder.class);
|
assertThat(context).hasSingleBean(JwtDecoder.class);
|
||||||
assertThat(getBearerTokenFilter(context)).isNotNull();
|
assertThat(getBearerTokenFilter(context)).isNotNull();
|
||||||
|
assertJwkSetUriJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertJwkSetUriJwtDecoderBuilderCustomization(AssertableWebApplicationContext context) {
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer customizer = context.getBean("decoderBuilderCustomizer",
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer.class);
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer anotherCustomizer = context.getBean("anotherDecoderBuilderCustomizer",
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer.class);
|
||||||
|
InOrder inOrder = inOrder(customizer, anotherCustomizer);
|
||||||
|
inOrder.verify(customizer).customize(any());
|
||||||
|
inOrder.verify(anotherCustomizer).customize(any());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void autoConfigurationShouldMatchDefaultJwsAlgorithm() {
|
void autoConfigurationShouldMatchDefaultJwsAlgorithm() {
|
||||||
this.contextRunner
|
this.contextRunner
|
||||||
|
@ -194,6 +209,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
||||||
.getField(supplierJwtDecoderBean, "delegate");
|
.getField(supplierJwtDecoderBean, "delegate");
|
||||||
jwtDecoderSupplier.get();
|
jwtDecoderSupplier.get();
|
||||||
|
assertJwkSetUriJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isEqualTo(2);
|
assertThat(this.server.getRequestCount()).isEqualTo(2);
|
||||||
|
@ -218,6 +234,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
||||||
.getField(supplierJwtDecoderBean, "delegate");
|
.getField(supplierJwtDecoderBean, "delegate");
|
||||||
jwtDecoderSupplier.get();
|
jwtDecoderSupplier.get();
|
||||||
|
assertJwkSetUriJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isEqualTo(3);
|
assertThat(this.server.getRequestCount()).isEqualTo(3);
|
||||||
|
@ -243,6 +260,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
|
||||||
.getField(supplierJwtDecoderBean, "delegate");
|
.getField(supplierJwtDecoderBean, "delegate");
|
||||||
jwtDecoderSupplier.get();
|
jwtDecoderSupplier.get();
|
||||||
|
assertJwkSetUriJwtDecoderBuilderCustomization(context);
|
||||||
});
|
});
|
||||||
// The last request is to the JWK Set endpoint to look up the algorithm
|
// The last request is to the JWK Set endpoint to look up the algorithm
|
||||||
assertThat(this.server.getRequestCount()).isEqualTo(4);
|
assertThat(this.server.getRequestCount()).isEqualTo(4);
|
||||||
|
@ -678,6 +696,18 @@ class OAuth2ResourceServerAutoConfigurationTests {
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
static class TestConfig {
|
static class TestConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(1)
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer decoderBuilderCustomizer() {
|
||||||
|
return mock(JwkSetUriJwtDecoderBuilderCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Order(2)
|
||||||
|
JwkSetUriJwtDecoderBuilderCustomizer anotherDecoderBuilderCustomizer() {
|
||||||
|
return mock(JwkSetUriJwtDecoderBuilderCustomizer.class);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
@Configuration(proxyBeanMethods = false)
|
||||||
|
|
Loading…
Reference in New Issue