From 4cfe59cd85197fff7732bea8576d519510965eb2 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Wed, 8 May 2024 06:16:05 -0400 Subject: [PATCH] Path component for issuer identifier should be disabled by default Issue gh-1342 Closes gh-1611 --- .../AuthorizationServerSettingsConfig.java | 30 ++++++++++ ...OAuth2AuthorizationEndpointConfigurer.java | 13 +++-- .../OAuth2AuthorizationServerConfigurer.java | 15 +++-- ...ationServerMetadataEndpointConfigurer.java | 8 ++- .../OAuth2ClientAuthenticationConfigurer.java | 30 +++++----- .../configurers/OAuth2ConfigurerUtils.java | 2 +- ...DeviceAuthorizationEndpointConfigurer.java | 14 +++-- ...2DeviceVerificationEndpointConfigurer.java | 13 +++-- .../OAuth2TokenEndpointConfigurer.java | 15 +++-- ...2TokenIntrospectionEndpointConfigurer.java | 15 +++-- ...uth2TokenRevocationEndpointConfigurer.java | 14 +++-- ...cClientRegistrationEndpointConfigurer.java | 13 +++-- .../OidcLogoutEndpointConfigurer.java | 13 +++-- ...oviderConfigurationEndpointConfigurer.java | 8 ++- .../OidcUserInfoEndpointConfigurer.java | 13 +++-- .../context/AuthorizationServerContext.java | 2 +- ...dcProviderConfigurationEndpointFilter.java | 17 ++++-- .../settings/AuthorizationServerSettings.java | 55 ++++++++++++++++++- .../settings/ConfigurationSettingNames.java | 6 ++ ...orizationServerMetadataEndpointFilter.java | 17 ++++-- .../web/configurers/JwkSetTests.java | 7 ++- .../OAuth2AuthorizationCodeGrantTests.java | 13 ++++- ...Auth2AuthorizationServerMetadataTests.java | 6 +- .../OAuth2ClientCredentialsGrantTests.java | 14 ++++- .../OAuth2DeviceCodeGrantTests.java | 16 +++++- .../OAuth2TokenIntrospectionTests.java | 25 ++++++--- .../OAuth2TokenRevocationTests.java | 14 ++++- .../OidcClientRegistrationTests.java | 1 + .../OidcProviderConfigurationTests.java | 14 ++++- .../annotation/web/configurers/OidcTests.java | 2 +- .../web/configurers/OidcUserInfoTests.java | 1 + ...viderConfigurationEndpointFilterTests.java | 7 +++ .../AuthorizationServerSettingsTests.java | 30 +++++++++- ...tionServerMetadataEndpointFilterTests.java | 7 +++ .../config/AuthorizationServerConfig.java | 2 +- 35 files changed, 365 insertions(+), 107 deletions(-) create mode 100644 docs/src/main/java/sample/multitenancy/AuthorizationServerSettingsConfig.java diff --git a/docs/src/main/java/sample/multitenancy/AuthorizationServerSettingsConfig.java b/docs/src/main/java/sample/multitenancy/AuthorizationServerSettingsConfig.java new file mode 100644 index 00000000..89d53f20 --- /dev/null +++ b/docs/src/main/java/sample/multitenancy/AuthorizationServerSettingsConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright 2020-2024 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 sample.multitenancy; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; + +@Configuration(proxyBeanMethods = false) +public class AuthorizationServerSettingsConfig { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + } + +} diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java index 354b02b7..065ce435 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java @@ -51,7 +51,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Authorization Endpoint. @@ -211,7 +211,9 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - String authorizationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getAuthorizationEndpoint()); + String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) : + authorizationServerSettings.getAuthorizationEndpoint(); this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.GET.name()), new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.POST.name())); @@ -229,11 +231,12 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C void configure(HttpSecurity httpSecurity) { AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) : + authorizationServerSettings.getAuthorizationEndpoint(); OAuth2AuthorizationEndpointFilter authorizationEndpointFilter = - new OAuth2AuthorizationEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getAuthorizationEndpoint())); + new OAuth2AuthorizationEndpointFilter(authenticationManager, authorizationEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.authorizationRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.authorizationRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java index ff0e7112..b1d642e7 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java @@ -56,7 +56,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * An {@link AbstractHttpConfigurer} for OAuth 2.0 Authorization Server support. @@ -315,8 +315,10 @@ public final class OAuth2AuthorizationServerConfigurer configurer.init(httpSecurity); requestMatchers.add(configurer.getRequestMatcher()); }); - requestMatchers.add(new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getJwkSetEndpoint()), HttpMethod.GET.name())); + String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) : + authorizationServerSettings.getJwkSetEndpoint(); + requestMatchers.add(new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name())); this.endpointsMatcher = new OrRequestMatcher(requestMatchers); ExceptionHandlingConfigurer exceptionHandling = httpSecurity.getConfigurer(ExceptionHandlingConfigurer.class); @@ -343,8 +345,11 @@ public final class OAuth2AuthorizationServerConfigurer JWKSource jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity); if (jwkSource != null) { - NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter( - jwkSource, withMultipleIssuerPattern(authorizationServerSettings.getJwkSetEndpoint())); + String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) : + authorizationServerSettings.getJwkSetEndpoint(); + NimbusJwkSetEndpointFilter jwkSetEndpointFilter = + new NimbusJwkSetEndpointFilter(jwkSource, jwkSetEndpointUri); httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java index ace3388f..c619d4ef 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java @@ -21,6 +21,7 @@ import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationServerMetadataEndpointFilter; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -69,8 +70,11 @@ public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends A @Override void init(HttpSecurity httpSecurity) { - this.requestMatcher = new AntPathRequestMatcher( - "/.well-known/oauth-authorization-server/**", HttpMethod.GET.name()); + AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String authorizationServerMetadataEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + "/.well-known/oauth-authorization-server/**" : + "/.well-known/oauth-authorization-server"; + this.requestMatcher = new AntPathRequestMatcher(authorizationServerMetadataEndpointUri, HttpMethod.GET.name()); } @Override diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java index 90559d36..9a85d746 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java @@ -53,7 +53,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for OAuth 2.0 Client Authentication. @@ -163,19 +163,23 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) : + authorizationServerSettings.getTokenEndpoint(); + String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) : + authorizationServerSettings.getTokenIntrospectionEndpoint(); + String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) : + authorizationServerSettings.getTokenRevocationEndpoint(); + String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) : + authorizationServerSettings.getDeviceAuthorizationEndpoint(); this.requestMatcher = new OrRequestMatcher( - new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint()), - HttpMethod.POST.name()), - new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()), - HttpMethod.POST.name()), - new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint()), - HttpMethod.POST.name()), - new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()), - HttpMethod.POST.name())); + new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()), + new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()), + new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()), + new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name())); List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); if (!this.authenticationProviders.isEmpty()) { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java index 2ff97378..5238b655 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java @@ -57,7 +57,7 @@ final class OAuth2ConfigurerUtils { private OAuth2ConfigurerUtils() { } - static String withMultipleIssuerPattern(String endpointUri) { + static String withMultipleIssuersPattern(String endpointUri) { Assert.hasText(endpointUri, "endpointUri cannot be empty"); return endpointUri.startsWith("/") ? "/**" + endpointUri : diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java index af86410c..72cf5228 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java @@ -45,7 +45,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Device Authorization Endpoint. @@ -167,8 +167,10 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO public void init(HttpSecurity builder) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder); - this.requestMatcher = new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()), HttpMethod.POST.name()); + String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) : + authorizationServerSettings.getDeviceAuthorizationEndpoint(); + this.requestMatcher = new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()); List authenticationProviders = createDefaultAuthenticationProviders(builder); if (!this.authenticationProviders.isEmpty()) { @@ -184,9 +186,11 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder); + String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) : + authorizationServerSettings.getDeviceAuthorizationEndpoint(); OAuth2DeviceAuthorizationEndpointFilter deviceAuthorizationEndpointFilter = - new OAuth2DeviceAuthorizationEndpointFilter( - authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())); + new OAuth2DeviceAuthorizationEndpointFilter(authenticationManager, deviceAuthorizationEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.deviceAuthorizationRequestConverters.isEmpty()) { diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java index aa4d7b78..af1c0d39 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java @@ -50,7 +50,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Device Verification Endpoint. @@ -197,7 +197,9 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA public void init(HttpSecurity builder) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder); - String deviceVerificationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getDeviceVerificationEndpoint()); + String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) : + authorizationServerSettings.getDeviceVerificationEndpoint(); this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.GET.name()), new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.POST.name())); @@ -217,10 +219,11 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder); + String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) : + authorizationServerSettings.getDeviceVerificationEndpoint(); OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter = - new OAuth2DeviceVerificationEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getDeviceVerificationEndpoint())); + new OAuth2DeviceVerificationEndpointFilter(authenticationManager, deviceVerificationEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.deviceVerificationRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.deviceVerificationRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java index 00a7d62b..b18703af 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java @@ -56,7 +56,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Token Endpoint. @@ -166,8 +166,10 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - this.requestMatcher = new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint()), HttpMethod.POST.name()); + String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) : + authorizationServerSettings.getTokenEndpoint(); + this.requestMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()); List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); if (!this.authenticationProviders.isEmpty()) { @@ -183,10 +185,11 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) : + authorizationServerSettings.getTokenEndpoint(); OAuth2TokenEndpointFilter tokenEndpointFilter = - new OAuth2TokenEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getTokenEndpoint())); + new OAuth2TokenEndpointFilter(authenticationManager, tokenEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.accessTokenRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.accessTokenRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java index 96eb1062..c7ec893a 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java @@ -43,7 +43,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Token Introspection Endpoint. @@ -153,8 +153,10 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - this.requestMatcher = new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()), HttpMethod.POST.name()); + String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) : + authorizationServerSettings.getTokenIntrospectionEndpoint(); + this.requestMatcher = new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()); List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); if (!this.authenticationProviders.isEmpty()) { @@ -169,10 +171,11 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA void configure(HttpSecurity httpSecurity) { AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - + String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) : + authorizationServerSettings.getTokenIntrospectionEndpoint(); OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter = - new OAuth2TokenIntrospectionEndpointFilter( - authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getTokenIntrospectionEndpoint())); + new OAuth2TokenIntrospectionEndpointFilter(authenticationManager, tokenIntrospectionEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.introspectionRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.introspectionRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java index 9b89d8d0..f7d9abaa 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java @@ -42,7 +42,7 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for the OAuth 2.0 Token Revocation Endpoint. @@ -152,8 +152,10 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - this.requestMatcher = new AntPathRequestMatcher( - withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint()), HttpMethod.POST.name()); + String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) : + authorizationServerSettings.getTokenRevocationEndpoint(); + this.requestMatcher = new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()); List authenticationProviders = createDefaultAuthenticationProviders(httpSecurity); if (!this.authenticationProviders.isEmpty()) { @@ -169,9 +171,11 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) : + authorizationServerSettings.getTokenRevocationEndpoint(); OAuth2TokenRevocationEndpointFilter revocationEndpointFilter = - new OAuth2TokenRevocationEndpointFilter( - authenticationManager, withMultipleIssuerPattern(authorizationServerSettings.getTokenRevocationEndpoint())); + new OAuth2TokenRevocationEndpointFilter(authenticationManager, tokenRevocationEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.revocationRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.revocationRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java index b030353e..6cd1f88c 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java @@ -46,7 +46,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for OpenID Connect 1.0 Dynamic Client Registration Endpoint. @@ -162,7 +162,9 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - String clientRegistrationEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()); + String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) : + authorizationServerSettings.getOidcClientRegistrationEndpoint(); this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.POST.name()), new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.GET.name()) @@ -182,10 +184,11 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) : + authorizationServerSettings.getOidcClientRegistrationEndpoint(); OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter = - new OidcClientRegistrationEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint())); + new OidcClientRegistrationEndpointFilter(authenticationManager, clientRegistrationEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.clientRegistrationRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.clientRegistrationRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java index f138f1e2..3b8aa135 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java @@ -44,7 +44,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for OpenID Connect 1.0 RP-Initiated Logout Endpoint. @@ -153,7 +153,9 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - String logoutEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcLogoutEndpoint()); + String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) : + authorizationServerSettings.getOidcLogoutEndpoint(); this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.GET.name()), new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.POST.name()) @@ -173,10 +175,11 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) : + authorizationServerSettings.getOidcLogoutEndpoint(); OidcLogoutEndpointFilter oidcLogoutEndpointFilter = - new OidcLogoutEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getOidcLogoutEndpoint())); + new OidcLogoutEndpointFilter(authenticationManager, logoutEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.logoutRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.logoutRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java index 9e02d84b..38abeb92 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java @@ -22,6 +22,7 @@ import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; import org.springframework.security.oauth2.server.authorization.oidc.web.OidcProviderConfigurationEndpointFilter; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; @@ -69,8 +70,11 @@ public final class OidcProviderConfigurationEndpointConfigurer extends AbstractO @Override void init(HttpSecurity httpSecurity) { - this.requestMatcher = new AntPathRequestMatcher( - "/**/.well-known/openid-configuration", HttpMethod.GET.name()); + AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String oidcProviderConfigurationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + "/**/.well-known/openid-configuration" : + "/.well-known/openid-configuration"; + this.requestMatcher = new AntPathRequestMatcher(oidcProviderConfigurationEndpointUri, HttpMethod.GET.name()); } @Override diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java index 79f8883c..16c0f5e6 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java @@ -49,7 +49,7 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuerPattern; +import static org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2ConfigurerUtils.withMultipleIssuersPattern; /** * Configurer for OpenID Connect 1.0 UserInfo Endpoint. @@ -187,7 +187,9 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur @Override void init(HttpSecurity httpSecurity) { AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); - String userInfoEndpointUri = withMultipleIssuerPattern(authorizationServerSettings.getOidcUserInfoEndpoint()); + String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) : + authorizationServerSettings.getOidcUserInfoEndpoint(); this.requestMatcher = new OrRequestMatcher( new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()), new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name())); @@ -206,10 +208,11 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class); AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity); + String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ? + withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) : + authorizationServerSettings.getOidcUserInfoEndpoint(); OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter = - new OidcUserInfoEndpointFilter( - authenticationManager, - withMultipleIssuerPattern(authorizationServerSettings.getOidcUserInfoEndpoint())); + new OidcUserInfoEndpointFilter(authenticationManager, userInfoEndpointUri); List authenticationConverters = createDefaultAuthenticationConverters(); if (!this.userInfoRequestConverters.isEmpty()) { authenticationConverters.addAll(0, this.userInfoRequestConverters); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java index 3a3a556d..bb2b19f2 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java @@ -32,7 +32,7 @@ public interface AuthorizationServerContext { * resolves the issuer identifier from the "current" request. * *

- * The issuer identifier may contain a path component to support multiple issuers per host in a multi-tenant hosting configuration. + * The issuer identifier may contain a path component to support {@link AuthorizationServerSettings#isMultipleIssuersAllowed() multiple issuers per host} in a multi-tenant hosting configuration. * *

* For example: diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java index 3c041669..c2829df0 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java @@ -57,11 +57,9 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques /** * The default endpoint {@code URI} for OpenID Provider Configuration requests. */ - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/**/.well-known/openid-configuration"; + private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - private final RequestMatcher requestMatcher = new AntPathRequestMatcher( - DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, - HttpMethod.GET.name()); + private final RequestMatcher requestMatcher = createRequestMatcher(); private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter = new OidcProviderConfigurationHttpMessageConverter(); private Consumer providerConfigurationCustomizer = (providerConfiguration) -> {}; @@ -123,6 +121,17 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques providerConfiguration.build(), MediaType.APPLICATION_JSON, httpResponse); } + private static RequestMatcher createRequestMatcher() { + final RequestMatcher defaultRequestMatcher = new AntPathRequestMatcher( + DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name()); + final RequestMatcher multipleIssuersRequestMatcher = new AntPathRequestMatcher( + "/**" + DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name()); + return (request) -> + AuthorizationServerContextHolder.getContext().getAuthorizationServerSettings().isMultipleIssuersAllowed() ? + multipleIssuersRequestMatcher.matches(request) : + defaultRequestMatcher.matches(request); + } + private static Consumer> clientAuthenticationMethods() { return (authenticationMethods) -> { authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java index 4698da24..4ab6d4fb 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.settings; import java.util.Map; +import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContext; import org.springframework.util.Assert; /** @@ -43,6 +44,25 @@ public final class AuthorizationServerSettings extends AbstractSettings { return getSetting(ConfigurationSettingNames.AuthorizationServer.ISSUER); } + /** + * Returns {@code true} if multiple issuers are allowed per host. The default is {@code false}. + * Using path components in the URL of the issuer identifier enables supporting multiple issuers per host in a multi-tenant hosting configuration. + * + *

+ * For example: + *

    + *
  • {@code https://example.com/issuer1}
  • + *
  • {@code https://example.com/authz/issuer2}
  • + *
+ * + * @return {@code true} if multiple issuers are allowed per host, {@code false} otherwise + * @since 1.3 + * @see AuthorizationServerContext#getIssuer() + */ + public boolean isMultipleIssuersAllowed() { + return getSetting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED); + } + /** * Returns the OAuth 2.0 Authorization endpoint. The default is {@code /oauth2/authorize}. * @@ -143,6 +163,7 @@ public final class AuthorizationServerSettings extends AbstractSettings { */ public static Builder builder() { return new Builder() + .multipleIssuersAllowed(false) .authorizationEndpoint("/oauth2/authorize") .deviceAuthorizationEndpoint("/oauth2/device_authorization") .deviceVerificationEndpoint("/oauth2/device_verification") @@ -185,6 +206,31 @@ public final class AuthorizationServerSettings extends AbstractSettings { return setting(ConfigurationSettingNames.AuthorizationServer.ISSUER, issuer); } + /** + * Set to {@code true} if multiple issuers are allowed per host. + * Using path components in the URL of the issuer identifier enables supporting multiple issuers per host in a multi-tenant hosting configuration. + * + *

+ * For example: + *

    + *
  • {@code https://example.com/issuer1}
  • + *
  • {@code https://example.com/authz/issuer2}
  • + *
+ * + *

+ * NOTE: Explicitly configuring the issuer identifier via {@link #issuer(String)} forces to a single-tenant configuration. + * Avoid configuring the issuer identifier when using a multi-tenant hosting configuration, + * allowing the issuer identifier to be resolved from the "current" request. + * + * @param multipleIssuersAllowed {@code true} if multiple issuers are allowed per host, {@code false} otherwise + * @return the {@link Builder} for further configuration + * @since 1.3 + * @see AuthorizationServerContext#getIssuer() + */ + public Builder multipleIssuersAllowed(boolean multipleIssuersAllowed) { + return setting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED, multipleIssuersAllowed); + } + /** * Sets the OAuth 2.0 Authorization endpoint. * @@ -295,7 +341,12 @@ public final class AuthorizationServerSettings extends AbstractSettings { */ @Override public AuthorizationServerSettings build() { - return new AuthorizationServerSettings(getSettings()); + AuthorizationServerSettings authorizationServerSettings = new AuthorizationServerSettings(getSettings()); + if (authorizationServerSettings.getIssuer() != null && authorizationServerSettings.isMultipleIssuersAllowed()) { + throw new IllegalArgumentException("The issuer identifier (" + authorizationServerSettings.getIssuer() + + ") cannot be set when isMultipleIssuersAllowed() is true."); + } + return authorizationServerSettings; } } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java index 27f5c609..2e3b8532 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java @@ -88,6 +88,12 @@ public final class ConfigurationSettingNames { */ public static final String ISSUER = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("issuer"); + /** + * Set to {@code true} if multiple issuers are allowed per host. + * @since 1.3 + */ + public static final String MULTIPLE_ISSUERS_ALLOWED = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("multiple-issuers-allowed"); + /** * Set the OAuth 2.0 Authorization endpoint. */ diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java index bd576de7..8bb43fed 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java @@ -55,11 +55,9 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP /** * The default endpoint {@code URI} for OAuth 2.0 Authorization Server Metadata requests. */ - private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server/**"; + private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; - private final RequestMatcher requestMatcher = new AntPathRequestMatcher( - DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, - HttpMethod.GET.name()); + private final RequestMatcher requestMatcher = createRequestMatcher(); private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter(); private Consumer authorizationServerMetadataCustomizer = (authorizationServerMetadata) -> {}; @@ -116,6 +114,17 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP authorizationServerMetadata.build(), MediaType.APPLICATION_JSON, httpResponse); } + private static RequestMatcher createRequestMatcher() { + final RequestMatcher defaultRequestMatcher = new AntPathRequestMatcher( + DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name()); + final RequestMatcher multipleIssuersRequestMatcher = new AntPathRequestMatcher( + DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI + "/**", HttpMethod.GET.name()); + return (request) -> + AuthorizationServerContextHolder.getContext().getAuthorizationServerSettings().isMultipleIssuersAllowed() ? + multipleIssuersRequestMatcher.matches(request) : + defaultRequestMatcher.matches(request); + } + private static Consumer> clientAuthenticationMethods() { return (authenticationMethods) -> { authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java index 9c6fbfc5..1c942159 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java @@ -63,7 +63,6 @@ public class JwkSetTests { private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; private static EmbeddedDatabase db; private static JWKSource jwkSource; - private static AuthorizationServerSettings authorizationServerSettings; public final SpringTestContext spring = new SpringTestContext(); @@ -73,11 +72,13 @@ public class JwkSetTests { @Autowired private JdbcOperations jdbcOperations; + @Autowired + private AuthorizationServerSettings authorizationServerSettings; + @BeforeAll public static void init() { JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - authorizationServerSettings = AuthorizationServerSettings.builder().jwkSetEndpoint("/test/jwks").build(); db = new EmbeddedDatabaseBuilder() .generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) @@ -181,7 +182,7 @@ public class JwkSetTests { @Bean AuthorizationServerSettings authorizationServerSettings() { - return authorizationServerSettings; + return AuthorizationServerSettings.builder().jwkSetEndpoint("/test/jwks").multipleIssuersAllowed(true).build(); } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java index 1f9f5b02..6ec2a072 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java @@ -857,7 +857,7 @@ public class OAuth2AuthorizationCodeGrantTests { @Test public void requestWhenAuthorizationAndTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); this.registeredClientRepository.save(registeredClient); @@ -1260,4 +1260,15 @@ public class OAuth2AuthorizationCodeGrantTests { // @formatter:on } + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfigurationWithTokenGenerator { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + } + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java index a7330a1d..2d8d18fd 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java @@ -113,7 +113,7 @@ public class OAuth2AuthorizationServerMetadataTests { @Test public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithIssuerNotSet.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); String host = "https://example.com:8443"; @@ -216,11 +216,11 @@ public class OAuth2AuthorizationServerMetadataTests { @EnableWebSecurity @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithIssuerNotSet extends AuthorizationServerConfiguration { + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().build(); + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java index f5679124..8172d9e1 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java @@ -84,6 +84,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; @@ -398,7 +399,7 @@ public class OAuth2ClientCredentialsGrantTests { @Test public void requestWhenTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); this.registeredClientRepository.save(registeredClient); @@ -573,4 +574,15 @@ public class OAuth2ClientCredentialsGrantTests { } + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + } + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java index 3e82ccc1..55f5b1da 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java @@ -70,6 +70,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; import org.springframework.test.web.servlet.MockMvc; @@ -204,7 +205,7 @@ public class OAuth2DeviceCodeGrantTests { @Test public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() @@ -288,7 +289,7 @@ public class OAuth2DeviceCodeGrantTests { @Test public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); // @formatter:off RegisteredClient registeredClient = TestRegisteredClients.registeredClient() @@ -585,4 +586,15 @@ public class OAuth2DeviceCodeGrantTests { } + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + } + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java index faedfe6f..b89d43ed 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java @@ -123,7 +123,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @ExtendWith(SpringTestContextExtension.class) public class OAuth2TokenIntrospectionTests { private static EmbeddedDatabase db; - private static AuthorizationServerSettings authorizationServerSettings; private static OAuth2TokenCustomizer accessTokenCustomizer; private static AuthenticationConverter authenticationConverter; private static Consumer> authenticationConvertersConsumer; @@ -150,9 +149,11 @@ public class OAuth2TokenIntrospectionTests { @Autowired private OAuth2AuthorizationService authorizationService; + @Autowired + private AuthorizationServerSettings authorizationServerSettings; + @BeforeAll public static void init() { - authorizationServerSettings = AuthorizationServerSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); authenticationConverter = mock(AuthenticationConverter.class); authenticationConvertersConsumer = mock(Consumer.class); authenticationProvider = mock(AuthenticationProvider.class); @@ -225,7 +226,7 @@ public class OAuth2TokenIntrospectionTests { this.authorizationService.save(authorization); // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(authorizationServerSettings.getTokenIntrospectionEndpoint()) + MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) @@ -265,7 +266,7 @@ public class OAuth2TokenIntrospectionTests { this.authorizationService.save(authorization); // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(authorizationServerSettings.getTokenIntrospectionEndpoint()) + MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) @@ -307,7 +308,7 @@ public class OAuth2TokenIntrospectionTests { this.authorizationService.save(authorization); // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(authorizationServerSettings.getTokenEndpoint()) + MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenEndpoint()) .params(getAuthorizationCodeTokenRequestParameters(authorizedRegisteredClient, authorization)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(authorizedRegisteredClient))) .andExpect(status().isOk()) @@ -321,7 +322,7 @@ public class OAuth2TokenIntrospectionTests { this.registeredClientRepository.save(introspectRegisteredClient); // @formatter:off - mvcResult = this.mvc.perform(post(authorizationServerSettings.getTokenIntrospectionEndpoint()) + mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()) @@ -380,7 +381,7 @@ public class OAuth2TokenIntrospectionTests { when(authenticationProvider.authenticate(any())).thenReturn(tokenIntrospectionAuthentication); // @formatter:off - this.mvc.perform(post(authorizationServerSettings.getTokenIntrospectionEndpoint()) + this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()); @@ -437,7 +438,7 @@ public class OAuth2TokenIntrospectionTests { String issuer = "https://example.com:8443/issuer1"; // @formatter:off - this.mvc.perform(post(issuer.concat(authorizationServerSettings.getTokenIntrospectionEndpoint())) + this.mvc.perform(post(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())) .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) .andExpect(status().isOk()); @@ -517,7 +518,7 @@ public class OAuth2TokenIntrospectionTests { @Bean AuthorizationServerSettings authorizationServerSettings() { - return authorizationServerSettings; + return AuthorizationServerSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); } @Bean @@ -581,6 +582,12 @@ public class OAuth2TokenIntrospectionTests { } // @formatter:on + + @Override + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).tokenIntrospectionEndpoint("/test/introspect").build(); + } + } } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java index dc626a13..60356ef8 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java @@ -69,6 +69,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.test.SpringTestContext; import org.springframework.security.oauth2.server.authorization.test.SpringTestContextExtension; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; @@ -203,7 +204,7 @@ public class OAuth2TokenRevocationTests { @Test public void requestWhenRevokeAccessTokenAndRequestIncludesIssuerPathThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); this.registeredClientRepository.save(registeredClient); @@ -383,4 +384,15 @@ public class OAuth2TokenRevocationTests { } + @EnableWebSecurity + @Import(OAuth2AuthorizationServerConfiguration.class) + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { + + @Bean + AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + } + + } + } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java index be1ea066..24c1d554 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java @@ -735,6 +735,7 @@ public class OidcClientRegistrationTests { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() + .multipleIssuersAllowed(true) .build(); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java index 2efd8ed4..a453de1b 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java @@ -84,7 +84,7 @@ public class OidcProviderConfigurationTests { @Test public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithIssuerNotSet.class).autowire(); + this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); String issuer = "https://example.com:8443/issuer1"; this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) @@ -219,6 +219,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfiguration { @Bean @@ -245,11 +246,13 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity - static class AuthorizationServerConfigurationWithIssuerNotSet extends AuthorizationServerConfiguration { + @Configuration(proxyBeanMethods = false) + static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() + .multipleIssuersAllowed(true) .build(); } @@ -315,6 +318,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { @Bean @@ -324,6 +328,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { @Bean @@ -333,6 +338,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { @Bean @@ -342,6 +348,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { @Bean @@ -351,6 +358,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { @Bean @@ -360,6 +368,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { @Bean @@ -369,6 +378,7 @@ public class OidcProviderConfigurationTests { } @EnableWebSecurity + @Configuration(proxyBeanMethods = false) static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { @Bean diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java index a63475b0..4e38d28c 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java @@ -617,7 +617,7 @@ public class OidcTests { @Bean AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().build(); + return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); } @Bean diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java index 4a0e01b0..57bbaab6 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java @@ -531,6 +531,7 @@ public class OidcUserInfoTests { @Bean AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder() + .multipleIssuersAllowed(true) .build(); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java index 7964f68c..3805e722 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.oidc.web; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -60,6 +61,9 @@ public class OidcProviderConfigurationEndpointFilterTests { @Test public void doFilterWhenNotConfigurationRequestThenNotProcessed() throws Exception { + AuthorizationServerContextHolder.setContext( + new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); + String requestUri = "/path"; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); @@ -73,6 +77,9 @@ public class OidcProviderConfigurationEndpointFilterTests { @Test public void doFilterWhenConfigurationRequestPostThenNotProcessed() throws Exception { + AuthorizationServerContextHolder.setContext( + new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); + String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); request.setServletPath(requestUri); diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java index 9eb902aa..fc00b818 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2023 the original author or authors. + * Copyright 2020-2024 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. @@ -33,6 +33,7 @@ public class AuthorizationServerSettingsTests { AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); assertThat(authorizationServerSettings.getIssuer()).isNull(); + assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isFalse(); assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo("/oauth2/authorize"); assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo("/oauth2/token"); assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo("/oauth2/jwks"); @@ -69,6 +70,7 @@ public class AuthorizationServerSettingsTests { .build(); assertThat(authorizationServerSettings.getIssuer()).isEqualTo(issuer); + assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isFalse(); assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo(authorizationEndpoint); assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo(tokenEndpoint); assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo(jwkSetEndpoint); @@ -79,6 +81,30 @@ public class AuthorizationServerSettingsTests { assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo(oidcLogoutEndpoint); } + @Test + public void buildWhenIssuerSetAndMultipleIssuersAllowedTrueThenThrowIllegalArgumentException() { + String issuer = "https://example.com:9000"; + assertThatIllegalArgumentException() + .isThrownBy(() -> AuthorizationServerSettings.builder().issuer(issuer).multipleIssuersAllowed(true).build()) + .withMessage("The issuer identifier (" + issuer + ") cannot be set when isMultipleIssuersAllowed() is true."); + } + + @Test + public void buildWhenIssuerNotSetAndMultipleIssuersAllowedTrueThenDefaultsAreSet() { + AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); + + assertThat(authorizationServerSettings.getIssuer()).isNull(); + assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isTrue(); + assertThat(authorizationServerSettings.getAuthorizationEndpoint()).isEqualTo("/oauth2/authorize"); + assertThat(authorizationServerSettings.getTokenEndpoint()).isEqualTo("/oauth2/token"); + assertThat(authorizationServerSettings.getJwkSetEndpoint()).isEqualTo("/oauth2/jwks"); + assertThat(authorizationServerSettings.getTokenRevocationEndpoint()).isEqualTo("/oauth2/revoke"); + assertThat(authorizationServerSettings.getTokenIntrospectionEndpoint()).isEqualTo("/oauth2/introspect"); + assertThat(authorizationServerSettings.getOidcClientRegistrationEndpoint()).isEqualTo("/connect/register"); + assertThat(authorizationServerSettings.getOidcUserInfoEndpoint()).isEqualTo("/userinfo"); + assertThat(authorizationServerSettings.getOidcLogoutEndpoint()).isEqualTo("/connect/logout"); + } + @Test public void settingWhenCustomThenSet() { AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() @@ -86,7 +112,7 @@ public class AuthorizationServerSettingsTests { .settings(settings -> settings.put("name2", "value2")) .build(); - assertThat(authorizationServerSettings.getSettings()).hasSize(12); + assertThat(authorizationServerSettings.getSettings()).hasSize(13); assertThat(authorizationServerSettings.getSetting("name1")).isEqualTo("value1"); assertThat(authorizationServerSettings.getSetting("name2")).isEqualTo("value2"); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java index fbc2b12e..65c94850 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java @@ -18,6 +18,7 @@ package org.springframework.security.oauth2.server.authorization.web; import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -60,6 +61,9 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { @Test public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception { + AuthorizationServerContextHolder.setContext( + new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); + String requestUri = "/path"; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); @@ -73,6 +77,9 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests { @Test public void doFilterWhenAuthorizationServerMetadataRequestPostThenNotProcessed() throws Exception { + AuthorizationServerContextHolder.setContext( + new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null)); + String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri); request.setServletPath(requestUri); diff --git a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java index f0d8b328..5ed3a561 100644 --- a/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java +++ b/samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java @@ -93,7 +93,7 @@ public class AuthorizationServerConfig { */ DeviceClientAuthenticationConverter deviceClientAuthenticationConverter = new DeviceClientAuthenticationConverter( - "/**" + authorizationServerSettings.getDeviceAuthorizationEndpoint()); + authorizationServerSettings.getDeviceAuthorizationEndpoint()); DeviceClientAuthenticationProvider deviceClientAuthenticationProvider = new DeviceClientAuthenticationProvider(registeredClientRepository);