Skip to content

Commit e571894

Browse files
committed
Merge pull request #28169 from Pooja199
* pr/28169: Auto-configure JwtSupplierDecoder to defer OIDC lookup Closes gh-28169
2 parents 1ff900c + 4e9f536 commit e571894

File tree

5 files changed

+56
-15
lines changed

5 files changed

+56
-15
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
3838
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
3939
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
40+
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
4041
import org.springframework.security.web.server.SecurityWebFilterChain;
4142

4243
/**
@@ -91,8 +92,9 @@ private byte[] getKeySpec(String keyValue) {
9192

9293
@Bean
9394
@Conditional(IssuerUriCondition.class)
94-
ReactiveJwtDecoder jwtDecoderByIssuerUri() {
95-
return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
95+
SupplierReactiveJwtDecoder jwtDecoderByIssuerUri() {
96+
return new SupplierReactiveJwtDecoder(
97+
() -> ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()));
9698
}
9799

98100
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.springframework.security.oauth2.jwt.JwtDecoders;
4040
import org.springframework.security.oauth2.jwt.JwtValidators;
4141
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
42+
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
4243
import org.springframework.security.web.SecurityFilterChain;
4344

4445
/**
@@ -91,8 +92,8 @@ private byte[] getKeySpec(String keyValue) {
9192

9293
@Bean
9394
@Conditional(IssuerUriCondition.class)
94-
JwtDecoder jwtDecoderByIssuerUri() {
95-
return JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
95+
SupplierJwtDecoder jwtDecoderByIssuerUri() {
96+
return new SupplierJwtDecoder(() -> JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()));
9697
}
9798

9899
}

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

+27-7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import okhttp3.mockwebserver.MockWebServer;
3232
import org.junit.jupiter.api.AfterEach;
3333
import org.junit.jupiter.api.Test;
34+
import reactor.core.publisher.Mono;
3435

3536
import org.springframework.boot.autoconfigure.AutoConfigurations;
3637
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -52,6 +53,7 @@
5253
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
5354
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
5455
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
56+
import org.springframework.security.oauth2.jwt.SupplierReactiveJwtDecoder;
5557
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
5658
import org.springframework.security.oauth2.server.resource.authentication.JwtReactiveAuthenticationManager;
5759
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenReactiveAuthenticationManager;
@@ -129,6 +131,7 @@ void autoConfigurationUsingPublicKeyValueShouldConfigureResourceServerUsingJwsAl
129131
}
130132

131133
@Test
134+
@SuppressWarnings("unchecked")
132135
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {
133136
this.server = new MockWebServer();
134137
this.server.start();
@@ -138,15 +141,21 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws I
138141
setupMockResponse(cleanIssuerPath);
139142
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
140143
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
141-
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
144+
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
142145
assertFilterConfiguredWithJwtAuthenticationManager(context);
143146
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
147+
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
148+
.getBean(SupplierReactiveJwtDecoder.class);
149+
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
150+
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
151+
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
144152
});
145153
// The last request is to the JWK Set endpoint to look up the algorithm
146-
assertThat(this.server.getRequestCount()).isEqualTo(2);
154+
assertThat(this.server.getRequestCount()).isEqualTo(1);
147155
}
148156

149157
@Test
158+
@SuppressWarnings("unchecked")
150159
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
151160
this.server = new MockWebServer();
152161
this.server.start();
@@ -155,15 +164,21 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() t
155164
setupMockResponsesWithErrors(cleanIssuerPath, 1);
156165
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
157166
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
158-
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
167+
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
159168
assertFilterConfiguredWithJwtAuthenticationManager(context);
160169
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
170+
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
171+
.getBean(SupplierReactiveJwtDecoder.class);
172+
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
173+
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
174+
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
161175
});
162176
// The last request is to the JWK Set endpoint to look up the algorithm
163-
assertThat(this.server.getRequestCount()).isEqualTo(3);
177+
assertThat(this.server.getRequestCount()).isEqualTo(2);
164178
}
165179

166180
@Test
181+
@SuppressWarnings("unchecked")
167182
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
168183
this.server = new MockWebServer();
169184
this.server.start();
@@ -172,12 +187,17 @@ void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws
172187
setupMockResponsesWithErrors(cleanIssuerPath, 2);
173188
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
174189
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
175-
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
190+
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
176191
assertFilterConfiguredWithJwtAuthenticationManager(context);
177192
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
193+
SupplierReactiveJwtDecoder supplierReactiveJwtDecoder = context
194+
.getBean(SupplierReactiveJwtDecoder.class);
195+
Mono<ReactiveJwtDecoder> reactiveJwtDecoderSupplier = (Mono<ReactiveJwtDecoder>) ReflectionTestUtils
196+
.getField(supplierReactiveJwtDecoder, "jwtDecoderMono");
197+
ReactiveJwtDecoder reactiveJwtDecoder = reactiveJwtDecoderSupplier.block();
178198
});
179199
// The last request is to the JWK Set endpoint to look up the algorithm
180-
assertThat(this.server.getRequestCount()).isEqualTo(4);
200+
assertThat(this.server.getRequestCount()).isEqualTo(3);
181201
}
182202

183203
@Test
@@ -228,7 +248,7 @@ void autoConfigurationWhenKeyLocationAndIssuerUriPresentShouldUseIssuerUri() thr
228248
+ this.server.getPort(),
229249
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location")
230250
.run((context) -> {
231-
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
251+
assertThat(context).hasSingleBean(SupplierReactiveJwtDecoder.class);
232252
assertFilterConfiguredWithJwtAuthenticationManager(context);
233253
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
234254
});

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

+21-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.HashMap;
2222
import java.util.List;
2323
import java.util.Map;
24+
import java.util.function.Supplier;
2425

2526
import javax.servlet.Filter;
2627

@@ -49,6 +50,7 @@
4950
import org.springframework.security.oauth2.jwt.Jwt;
5051
import org.springframework.security.oauth2.jwt.JwtDecoder;
5152
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
53+
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
5254
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
5355
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
5456
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
@@ -127,6 +129,7 @@ void autoConfigurationShouldConfigureResourceServerWithJwsAlgorithm() {
127129
}
128130

129131
@Test
132+
@SuppressWarnings("unchecked")
130133
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception {
131134
this.server = new MockWebServer();
132135
this.server.start();
@@ -136,14 +139,19 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws E
136139
setupMockResponse(cleanIssuerPath);
137140
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
138141
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
139-
assertThat(context).hasSingleBean(JwtDecoder.class);
142+
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
140143
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
144+
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
145+
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
146+
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
147+
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
141148
});
142149
// The last request is to the JWK Set endpoint to look up the algorithm
143150
assertThat(this.server.getRequestCount()).isEqualTo(2);
144151
}
145152

146153
@Test
154+
@SuppressWarnings("unchecked")
147155
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
148156
this.server = new MockWebServer();
149157
this.server.start();
@@ -153,25 +161,35 @@ void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() t
153161
setupMockResponsesWithErrors(cleanIssuerPath, 1);
154162
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
155163
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
156-
assertThat(context).hasSingleBean(JwtDecoder.class);
164+
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
157165
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
166+
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
167+
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
168+
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
169+
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
158170
});
159171
// The last request is to the JWK Set endpoint to look up the algorithm
160172
assertThat(this.server.getRequestCount()).isEqualTo(3);
161173
}
162174

163175
@Test
176+
@SuppressWarnings("unchecked")
164177
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
165178
this.server = new MockWebServer();
166179
this.server.start();
167180
String path = "test";
168181
String issuer = this.server.url(path).toString();
169182
String cleanIssuerPath = cleanIssuerPath(issuer);
170183
setupMockResponsesWithErrors(cleanIssuerPath, 2);
184+
171185
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
172186
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
173-
assertThat(context).hasSingleBean(JwtDecoder.class);
187+
assertThat(context).hasSingleBean(SupplierJwtDecoder.class);
174188
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
189+
SupplierJwtDecoder supplierJwtDecoderBean = context.getBean(SupplierJwtDecoder.class);
190+
Supplier<JwtDecoder> jwtDecoderSupplier = (Supplier<JwtDecoder>) ReflectionTestUtils
191+
.getField(supplierJwtDecoderBean, "jwtDecoderSupplier");
192+
JwtDecoder jwtDecoder = jwtDecoderSupplier.get();
175193
});
176194
// The last request is to the JWK Set endpoint to look up the algorithm
177195
assertThat(this.server.getRequestCount()).isEqualTo(4);

spring-boot-project/spring-boot-dependencies/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,7 @@ bom {
16931693
]
16941694
}
16951695
}
1696-
library("Spring Security", "5.6.0-M3") {
1696+
library("Spring Security", "5.6.0-SNAPSHOT") {
16971697
group("org.springframework.security") {
16981698
imports = [
16991699
"spring-security-bom"

0 commit comments

Comments
 (0)