Merge pull request #28169 from Pooja199

* pr/28169:
  Auto-configure JwtSupplierDecoder to defer OIDC lookup

Closes gh-28169
This commit is contained in:
Madhura Bhave 2021-10-06 16:40:04 -07:00
commit e571894903
5 changed files with 56 additions and 15 deletions

View File

@ -37,6 +37,7 @@ import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
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.web.server.SecurityWebFilterChain;
/**
@ -91,8 +92,9 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
@Bean
@Conditional(IssuerUriCondition.class)
ReactiveJwtDecoder jwtDecoderByIssuerUri() {
return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
SupplierReactiveJwtDecoder jwtDecoderByIssuerUri() {
return new SupplierReactiveJwtDecoder(
() -> ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()));
}
}

View File

@ -39,6 +39,7 @@ 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.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
/**
@ -91,8 +92,8 @@ class OAuth2ResourceServerJwtConfiguration {
@Bean
@Conditional(IssuerUriCondition.class)
JwtDecoder jwtDecoderByIssuerUri() {
return JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
SupplierJwtDecoder jwtDecoderByIssuerUri() {
return new SupplierJwtDecoder(() -> JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()));
}
}

View File

@ -31,6 +31,7 @@ import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
@ -52,6 +53,7 @@ import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
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.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
@ -129,6 +131,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {
this.server = new MockWebServer();
this.server.start();
@ -138,15 +141,21 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
setupMockResponse(cleanIssuerPath);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
.getBean(SupplierReactiveJwtDecoder.class);
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
});
// 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(1);
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
@ -155,15 +164,21 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
setupMockResponsesWithErrors(cleanIssuerPath, 1);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
.getBean(SupplierReactiveJwtDecoder.class);
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
});
// 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(2);
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
@ -172,12 +187,17 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
setupMockResponsesWithErrors(cleanIssuerPath, 2);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
.getBean(SupplierReactiveJwtDecoder.class);
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
});
// 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(3);
}
@Test
@ -228,7 +248,7 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
+ this.server.getPort(),
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
.run((context) -> {
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
});

View File

@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.Filter;
@ -49,6 +50,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
@ -127,6 +129,7 @@ class OAuth2ResourceServerAutoConfigurationTests {
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
@ -136,14 +139,19 @@ class OAuth2ResourceServerAutoConfigurationTests {
setupMockResponse(cleanIssuerPath);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
assertThat(context).hasSingleBean(JwtDecoder.class);
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
});
// The last request is to the JWK Set endpoint to look up the algorithm
assertThat(this.server.getRequestCount()).isEqualTo(2);
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
@ -153,14 +161,19 @@ class OAuth2ResourceServerAutoConfigurationTests {
setupMockResponsesWithErrors(cleanIssuerPath, 1);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
assertThat(context).hasSingleBean(JwtDecoder.class);
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
});
// The last request is to the JWK Set endpoint to look up the algorithm
assertThat(this.server.getRequestCount()).isEqualTo(3);
}
@Test
@SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
this.server = new MockWebServer();
this.server.start();
@ -168,10 +181,15 @@ class OAuth2ResourceServerAutoConfigurationTests {
String issuer = this.server.url(path).toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponsesWithErrors(cleanIssuerPath, 2);
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
assertThat(context).hasSingleBean(JwtDecoder.class);
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
});
// The last request is to the JWK Set endpoint to look up the algorithm
assertThat(this.server.getRequestCount()).isEqualTo(4);

View File

@ -1693,7 +1693,7 @@ bom {
]
}
}
library("Spring Security", "5.6.0-M3") {
library("Spring Security", "5.6.0-SNAPSHOT") {
group("org.springframework.security") {
imports = [
"spring-security-bom"