Skip to content

Commit 8baec96

Browse files
htztomicmbhave
authored andcommitted
Support RFC 8414 in JwtDecoders and ClientRegistrations
See gh-17761
1 parent f7f858b commit 8baec96

File tree

6 files changed

+204
-8
lines changed

6 files changed

+204
-8
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private static Builder getBuilderFromIssuerIfPossible(String registrationId, Str
8080
Provider provider = providers.get(providerId);
8181
String issuer = provider.getIssuerUri();
8282
if (issuer != null) {
83-
Builder builder = ClientRegistrations.fromOidcIssuerLocation(issuer).registrationId(registrationId);
83+
Builder builder = ClientRegistrations.fromIssuerLocation(issuer).registrationId(registrationId);
8484
return getBuilder(builder, provider);
8585
}
8686
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private byte[] getKeySpec(String keyValue) {
7979
@Bean
8080
@Conditional(IssuerUriCondition.class)
8181
ReactiveJwtDecoder jwtDecoderByIssuerUri() {
82-
return ReactiveJwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri());
82+
return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
8383
}
8484

8585
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ private byte[] getKeySpec(String keyValue) {
8181
@Bean
8282
@Conditional(IssuerUriCondition.class)
8383
JwtDecoder jwtDecoderByIssuerUri() {
84-
return JwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri());
84+
return JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
8585
}
8686

8787
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,22 @@ void oidcProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Ex
212212
testOidcConfiguration(login, "okta");
213213
}
214214

215+
@Test
216+
void oidcRfc8414ProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Exception {
217+
OAuth2ClientProperties.Registration login = new Registration();
218+
login.setClientId("clientId");
219+
login.setClientSecret("clientSecret");
220+
testOidcRfc8414Configuration(login, "okta");
221+
}
222+
223+
@Test
224+
void oAuthProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Exception {
225+
OAuth2ClientProperties.Registration login = new Registration();
226+
login.setClientId("clientId");
227+
login.setClientSecret("clientSecret");
228+
testOAuthConfiguration(login, "okta");
229+
}
230+
215231
@Test
216232
void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception {
217233
OAuth2ClientProperties.Registration login = new Registration();
@@ -221,6 +237,24 @@ void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Excep
221237
testOidcConfiguration(login, "okta-oidc");
222238
}
223239

240+
@Test
241+
void oidcRfc8414ProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception {
242+
OAuth2ClientProperties.Registration login = new Registration();
243+
login.setProvider("okta-oidcRfc8414");
244+
login.setClientId("clientId");
245+
login.setClientSecret("clientSecret");
246+
testOidcRfc8414Configuration(login, "okta-oidcRfc8414");
247+
}
248+
249+
@Test
250+
void oAuthProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception {
251+
OAuth2ClientProperties.Registration login = new Registration();
252+
login.setProvider("okta-oauth");
253+
login.setClientId("clientId");
254+
login.setClientSecret("clientSecret");
255+
testOAuthConfiguration(login, "okta-oauth");
256+
}
257+
224258
@Test
225259
void oidcProviderConfigurationWithCustomConfigurationOverridesProviderDefaults() throws Exception {
226260
this.server = new MockWebServer();
@@ -300,6 +334,70 @@ private void testOidcConfiguration(OAuth2ClientProperties.Registration registrat
300334
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
301335
assertThat(userInfoEndpoint.getAuthenticationMethod())
302336
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
337+
assertThat(this.server.getRequestCount()).isEqualTo(1);
338+
}
339+
340+
private void testOidcRfc8414Configuration(OAuth2ClientProperties.Registration registration, String providerId)
341+
throws Exception {
342+
this.server = new MockWebServer();
343+
this.server.start();
344+
String path = "test";
345+
String issuer = this.server.url(path).toString();
346+
setupMockResponseWithEmptyResponses(issuer, 1);
347+
OAuth2ClientProperties properties = new OAuth2ClientProperties();
348+
Provider provider = new Provider();
349+
provider.setIssuerUri(issuer);
350+
properties.getProvider().put(providerId, provider);
351+
properties.getRegistration().put("okta", registration);
352+
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
353+
.getClientRegistrations(properties);
354+
ClientRegistration adapted = registrations.get("okta");
355+
ProviderDetails providerDetails = adapted.getProviderDetails();
356+
assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
357+
assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
358+
assertThat(adapted.getRegistrationId()).isEqualTo("okta");
359+
assertThat(adapted.getClientName()).isEqualTo(issuer);
360+
assertThat(adapted.getScopes()).containsOnly("openid");
361+
assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
362+
assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
363+
assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
364+
UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
365+
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
366+
assertThat(userInfoEndpoint.getAuthenticationMethod())
367+
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
368+
assertThat(this.server.getRequestCount()).isEqualTo(2);
369+
370+
}
371+
372+
private void testOAuthConfiguration(OAuth2ClientProperties.Registration registration, String providerId)
373+
throws Exception {
374+
this.server = new MockWebServer();
375+
this.server.start();
376+
String path = "test";
377+
String issuer = this.server.url(path).toString();
378+
setupMockResponseWithEmptyResponses(issuer, 2);
379+
OAuth2ClientProperties properties = new OAuth2ClientProperties();
380+
Provider provider = new Provider();
381+
provider.setIssuerUri(issuer);
382+
properties.getProvider().put(providerId, provider);
383+
properties.getRegistration().put("okta", registration);
384+
Map<String, ClientRegistration> registrations = OAuth2ClientPropertiesRegistrationAdapter
385+
.getClientRegistrations(properties);
386+
ClientRegistration adapted = registrations.get("okta");
387+
ProviderDetails providerDetails = adapted.getProviderDetails();
388+
assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC);
389+
assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
390+
assertThat(adapted.getRegistrationId()).isEqualTo("okta");
391+
assertThat(adapted.getClientName()).isEqualTo(issuer);
392+
assertThat(adapted.getScopes()).containsOnly("openid");
393+
assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth");
394+
assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token");
395+
assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs");
396+
UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint();
397+
assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo");
398+
assertThat(userInfoEndpoint.getAuthenticationMethod())
399+
.isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER);
400+
assertThat(this.server.getRequestCount()).isEqualTo(3);
303401
}
304402

305403
private void setupMockResponse(String issuer) throws JsonProcessingException {
@@ -309,6 +407,15 @@ private void setupMockResponse(String issuer) throws JsonProcessingException {
309407
this.server.enqueue(mockResponse);
310408
}
311409

410+
private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse)
411+
throws JsonProcessingException {
412+
for (int i = 0; i < amountOfEmptyResponse; i++) {
413+
MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value());
414+
this.server.enqueue(emptyResponse);
415+
}
416+
setupMockResponse(issuer);
417+
}
418+
312419
private Map<String, Object> getResponse(String issuer) {
313420
Map<String, Object> response = new HashMap<>();
314421
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");

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

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,51 @@ void autoConfigurationShouldConfigureResourceServer() {
9494
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {
9595
this.server = new MockWebServer();
9696
this.server.start();
97-
String issuer = this.server.url("").toString();
97+
String path = "test";
98+
String issuer = this.server.url(path).toString();
9899
String cleanIssuerPath = cleanIssuerPath(issuer);
99100
setupMockResponse(cleanIssuerPath);
100101
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
101-
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
102+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
103+
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
104+
assertFilterConfiguredWithJwtAuthenticationManager(context);
105+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
106+
});
107+
assertThat(this.server.getRequestCount()).isEqualTo(1);
108+
}
109+
110+
@Test
111+
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
112+
this.server = new MockWebServer();
113+
this.server.start();
114+
String path = "test";
115+
String issuer = this.server.url(path).toString();
116+
String cleanIssuerPath = cleanIssuerPath(issuer);
117+
setupMockResponseWithEmptyResponses(cleanIssuerPath, 1);
118+
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
119+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
102120
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
103121
assertFilterConfiguredWithJwtAuthenticationManager(context);
122+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
104123
});
124+
assertThat(this.server.getRequestCount()).isEqualTo(2);
125+
}
126+
127+
@Test
128+
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
129+
this.server = new MockWebServer();
130+
this.server.start();
131+
String path = "test";
132+
String issuer = this.server.url(path).toString();
133+
String cleanIssuerPath = cleanIssuerPath(issuer);
134+
setupMockResponseWithEmptyResponses(cleanIssuerPath, 2);
135+
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
136+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
137+
assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class);
138+
assertFilterConfiguredWithJwtAuthenticationManager(context);
139+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
140+
});
141+
assertThat(this.server.getRequestCount()).isEqualTo(3);
105142
}
106143

107144
@Test
@@ -322,6 +359,15 @@ private void setupMockResponse(String issuer) throws JsonProcessingException {
322359
this.server.enqueue(mockResponse);
323360
}
324361

362+
private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse)
363+
throws JsonProcessingException {
364+
for (int i = 0; i < amountOfEmptyResponse; i++) {
365+
MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value());
366+
this.server.enqueue(emptyResponse);
367+
}
368+
setupMockResponse(issuer);
369+
}
370+
325371
private Map<String, Object> getResponse(String issuer) {
326372
Map<String, Object> response = new HashMap<>();
327373
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");

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

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,48 @@ void autoConfigurationShouldConfigureResourceServerWithJwsAlgorithm() {
114114
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception {
115115
this.server = new MockWebServer();
116116
this.server.start();
117-
String issuer = this.server.url("").toString();
117+
String path = "test";
118+
String issuer = this.server.url(path).toString();
118119
String cleanIssuerPath = cleanIssuerPath(issuer);
119120
setupMockResponse(cleanIssuerPath);
120121
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
121-
+ this.server.getHostName() + ":" + this.server.getPort()).run((context) -> {
122+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
122123
assertThat(context).hasSingleBean(JwtDecoder.class);
123-
assertThat(getBearerTokenFilter(context)).isNotNull();
124+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
125+
});
126+
assertThat(this.server.getRequestCount()).isEqualTo(1);
127+
}
128+
129+
@Test
130+
void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception {
131+
this.server = new MockWebServer();
132+
this.server.start();
133+
String path = "test";
134+
String issuer = this.server.url(path).toString();
135+
String cleanIssuerPath = cleanIssuerPath(issuer);
136+
setupMockResponseWithEmptyResponses(cleanIssuerPath, 1);
137+
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
138+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
139+
assertThat(context).hasSingleBean(JwtDecoder.class);
140+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
124141
});
142+
assertThat(this.server.getRequestCount()).isEqualTo(2);
143+
}
144+
145+
@Test
146+
void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception {
147+
this.server = new MockWebServer();
148+
this.server.start();
149+
String path = "test";
150+
String issuer = this.server.url(path).toString();
151+
String cleanIssuerPath = cleanIssuerPath(issuer);
152+
setupMockResponseWithEmptyResponses(cleanIssuerPath, 2);
153+
this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
154+
+ this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> {
155+
assertThat(context).hasSingleBean(JwtDecoder.class);
156+
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue();
157+
});
158+
assertThat(this.server.getRequestCount()).isEqualTo(3);
125159
}
126160

127161
@Test
@@ -306,6 +340,15 @@ private void setupMockResponse(String issuer) throws JsonProcessingException {
306340
this.server.enqueue(mockResponse);
307341
}
308342

343+
private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse)
344+
throws JsonProcessingException {
345+
for (int i = 0; i < amountOfEmptyResponse; i++) {
346+
MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value());
347+
this.server.enqueue(emptyResponse);
348+
}
349+
setupMockResponse(issuer);
350+
}
351+
309352
private Map<String, Object> getResponse(String issuer) {
310353
Map<String, Object> response = new HashMap<>();
311354
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");

0 commit comments

Comments
 (0)