diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java index 13feae1b36..481257a44a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapter.java @@ -80,7 +80,7 @@ public final class OAuth2ClientPropertiesRegistrationAdapter { Provider provider = providers.get(providerId); String issuer = provider.getIssuerUri(); if (issuer != null) { - Builder builder = ClientRegistrations.fromOidcIssuerLocation(issuer).registrationId(registrationId); + Builder builder = ClientRegistrations.fromIssuerLocation(issuer).registrationId(registrationId); return getBuilder(builder, provider); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java index 356c078621..a4f80a8925 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerJwkConfiguration.java @@ -79,7 +79,7 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { @Bean @Conditional(IssuerUriCondition.class) ReactiveJwtDecoder jwtDecoderByIssuerUri() { - return ReactiveJwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri()); + return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java index c7ad97955f..1bcb68eee8 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerJwtConfiguration.java @@ -81,7 +81,7 @@ class OAuth2ResourceServerJwtConfiguration { @Bean @Conditional(IssuerUriCondition.class) JwtDecoder jwtDecoderByIssuerUri() { - return JwtDecoders.fromOidcIssuerLocation(this.properties.getIssuerUri()); + return JwtDecoders.fromIssuerLocation(this.properties.getIssuerUri()); } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java index 0bf8877d6e..cf873de272 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/client/OAuth2ClientPropertiesRegistrationAdapterTests.java @@ -212,6 +212,22 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { testOidcConfiguration(login, "okta"); } + @Test + void oidcRfc8414ProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Exception { + OAuth2ClientProperties.Registration login = new Registration(); + login.setClientId("clientId"); + login.setClientSecret("clientSecret"); + testOidcRfc8414Configuration(login, "okta"); + } + + @Test + void oAuthProviderConfigurationWhenProviderNotSpecifiedOnRegistration() throws Exception { + OAuth2ClientProperties.Registration login = new Registration(); + login.setClientId("clientId"); + login.setClientSecret("clientSecret"); + testOAuthConfiguration(login, "okta"); + } + @Test void oidcProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception { OAuth2ClientProperties.Registration login = new Registration(); @@ -221,6 +237,24 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { testOidcConfiguration(login, "okta-oidc"); } + @Test + void oidcRfc8414ProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception { + OAuth2ClientProperties.Registration login = new Registration(); + login.setProvider("okta-oidcRfc8414"); + login.setClientId("clientId"); + login.setClientSecret("clientSecret"); + testOidcRfc8414Configuration(login, "okta-oidcRfc8414"); + } + + @Test + void oAuthProviderConfigurationWhenProviderSpecifiedOnRegistration() throws Exception { + OAuth2ClientProperties.Registration login = new Registration(); + login.setProvider("okta-oauth"); + login.setClientId("clientId"); + login.setClientSecret("clientSecret"); + testOAuthConfiguration(login, "okta-oauth"); + } + @Test void oidcProviderConfigurationWithCustomConfigurationOverridesProviderDefaults() throws Exception { this.server = new MockWebServer(); @@ -300,6 +334,70 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo"); assertThat(userInfoEndpoint.getAuthenticationMethod()) .isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER); + assertThat(this.server.getRequestCount()).isEqualTo(1); + } + + private void testOidcRfc8414Configuration(OAuth2ClientProperties.Registration registration, String providerId) + throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + setupMockResponseWithEmptyResponses(issuer, 1); + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Provider provider = new Provider(); + provider.setIssuerUri(issuer); + properties.getProvider().put(providerId, provider); + properties.getRegistration().put("okta", registration); + Map registrations = OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(properties); + ClientRegistration adapted = registrations.get("okta"); + ProviderDetails providerDetails = adapted.getProviderDetails(); + assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC); + assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(adapted.getRegistrationId()).isEqualTo("okta"); + assertThat(adapted.getClientName()).isEqualTo(issuer); + assertThat(adapted.getScopes()).containsOnly("openid"); + assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth"); + assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token"); + assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs"); + UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint(); + assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo"); + assertThat(userInfoEndpoint.getAuthenticationMethod()) + .isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER); + assertThat(this.server.getRequestCount()).isEqualTo(2); + + } + + private void testOAuthConfiguration(OAuth2ClientProperties.Registration registration, String providerId) + throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + setupMockResponseWithEmptyResponses(issuer, 2); + OAuth2ClientProperties properties = new OAuth2ClientProperties(); + Provider provider = new Provider(); + provider.setIssuerUri(issuer); + properties.getProvider().put(providerId, provider); + properties.getRegistration().put("okta", registration); + Map registrations = OAuth2ClientPropertiesRegistrationAdapter + .getClientRegistrations(properties); + ClientRegistration adapted = registrations.get("okta"); + ProviderDetails providerDetails = adapted.getProviderDetails(); + assertThat(adapted.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC); + assertThat(adapted.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); + assertThat(adapted.getRegistrationId()).isEqualTo("okta"); + assertThat(adapted.getClientName()).isEqualTo(issuer); + assertThat(adapted.getScopes()).containsOnly("openid"); + assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth"); + assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token"); + assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs"); + UserInfoEndpoint userInfoEndpoint = providerDetails.getUserInfoEndpoint(); + assertThat(userInfoEndpoint.getUri()).isEqualTo("https://example.com/oauth2/v3/userinfo"); + assertThat(userInfoEndpoint.getAuthenticationMethod()) + .isEqualTo(org.springframework.security.oauth2.core.AuthenticationMethod.HEADER); + assertThat(this.server.getRequestCount()).isEqualTo(3); } private void setupMockResponse(String issuer) throws JsonProcessingException { @@ -309,6 +407,15 @@ class OAuth2ClientPropertiesRegistrationAdapterTests { this.server.enqueue(mockResponse); } + private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse) + throws JsonProcessingException { + for (int i = 0; i < amountOfEmptyResponse; i++) { + MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); + this.server.enqueue(emptyResponse); + } + setupMockResponse(issuer); + } + private Map getResponse(String issuer) { Map response = new HashMap<>(); response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java index df8b13a82d..5253e5f879 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/reactive/ReactiveOAuth2ResourceServerAutoConfigurationTests.java @@ -94,14 +94,51 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException { this.server = new MockWebServer(); this.server.start(); - String issuer = this.server.url("").toString(); + String path = "test"; + String issuer = this.server.url(path).toString(); String cleanIssuerPath = cleanIssuerPath(issuer); setupMockResponse(cleanIssuerPath); this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" - + this.server.getHostName() + ":" + this.server.getPort()).run((context) -> { + + this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> { + assertThat(context).hasSingleBean(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + assertThat(this.server.getRequestCount()).isEqualTo(1); + } + + @Test + void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponseWithEmptyResponses(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(NimbusReactiveJwtDecoder.class); assertFilterConfiguredWithJwtAuthenticationManager(context); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); }); + assertThat(this.server.getRequestCount()).isEqualTo(2); + } + + @Test + void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponseWithEmptyResponses(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(NimbusReactiveJwtDecoder.class); + assertFilterConfiguredWithJwtAuthenticationManager(context); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + assertThat(this.server.getRequestCount()).isEqualTo(3); } @Test @@ -322,6 +359,15 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests { this.server.enqueue(mockResponse); } + private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse) + throws JsonProcessingException { + for (int i = 0; i < amountOfEmptyResponse; i++) { + MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); + this.server.enqueue(emptyResponse); + } + setupMockResponse(issuer); + } + private Map getResponse(String issuer) { Map response = new HashMap<>(); response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth"); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java index aabbfb591d..f62afd6f53 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/security/oauth2/resource/servlet/OAuth2ResourceServerAutoConfigurationTests.java @@ -114,14 +114,48 @@ class OAuth2ResourceServerAutoConfigurationTests { void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception { this.server = new MockWebServer(); this.server.start(); - String issuer = this.server.url("").toString(); + String path = "test"; + String issuer = this.server.url(path).toString(); String cleanIssuerPath = cleanIssuerPath(issuer); setupMockResponse(cleanIssuerPath); this.contextRunner.withPropertyValues("spring.security.oauth2.resourceserver.jwt.issuer-uri=http://" - + this.server.getHostName() + ":" + this.server.getPort()).run((context) -> { + + this.server.getHostName() + ":" + this.server.getPort() + "/" + path).run((context) -> { assertThat(context).hasSingleBean(JwtDecoder.class); - assertThat(getBearerTokenFilter(context)).isNotNull(); + assertThat(context.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + assertThat(this.server.getRequestCount()).isEqualTo(1); + } + + @Test + void autoConfigurationShouldConfigureResourceServerUsingOidcRfc8414IssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponseWithEmptyResponses(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.containsBean("jwtDecoderByIssuerUri")).isTrue(); }); + assertThat(this.server.getRequestCount()).isEqualTo(2); + } + + @Test + void autoConfigurationShouldConfigureResourceServerUsingOAuthIssuerUri() throws Exception { + this.server = new MockWebServer(); + this.server.start(); + String path = "test"; + String issuer = this.server.url(path).toString(); + String cleanIssuerPath = cleanIssuerPath(issuer); + setupMockResponseWithEmptyResponses(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.containsBean("jwtDecoderByIssuerUri")).isTrue(); + }); + assertThat(this.server.getRequestCount()).isEqualTo(3); } @Test @@ -306,6 +340,15 @@ class OAuth2ResourceServerAutoConfigurationTests { this.server.enqueue(mockResponse); } + private void setupMockResponseWithEmptyResponses(String issuer, int amountOfEmptyResponse) + throws JsonProcessingException { + for (int i = 0; i < amountOfEmptyResponse; i++) { + MockResponse emptyResponse = new MockResponse().setResponseCode(HttpStatus.NOT_FOUND.value()); + this.server.enqueue(emptyResponse); + } + setupMockResponse(issuer); + } + private Map getResponse(String issuer) { Map response = new HashMap<>(); response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");