浏览代码

Path component for issuer identifier should be disabled by default

Issue gh-1342

Closes gh-1611
Joe Grandja 1 年之前
父节点
当前提交
4cfe59cd85
共有 35 个文件被更改,包括 365 次插入107 次删除
  1. 30 0
      docs/src/main/java/sample/multitenancy/AuthorizationServerSettingsConfig.java
  2. 8 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationEndpointConfigurer.java
  3. 10 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerConfigurer.java
  4. 6 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataEndpointConfigurer.java
  5. 17 13
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientAuthenticationConfigurer.java
  6. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ConfigurerUtils.java
  7. 9 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceAuthorizationEndpointConfigurer.java
  8. 8 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceVerificationEndpointConfigurer.java
  9. 9 6
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenEndpointConfigurer.java
  10. 9 6
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionEndpointConfigurer.java
  11. 9 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationEndpointConfigurer.java
  12. 8 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationEndpointConfigurer.java
  13. 8 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcLogoutEndpointConfigurer.java
  14. 6 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationEndpointConfigurer.java
  15. 8 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoEndpointConfigurer.java
  16. 1 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/context/AuthorizationServerContext.java
  17. 13 4
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
  18. 53 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettings.java
  19. 6 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/settings/ConfigurationSettingNames.java
  20. 13 4
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java
  21. 4 3
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/JwkSetTests.java
  22. 12 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationCodeGrantTests.java
  23. 3 3
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2AuthorizationServerMetadataTests.java
  24. 13 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2ClientCredentialsGrantTests.java
  25. 14 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2DeviceCodeGrantTests.java
  26. 16 9
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenIntrospectionTests.java
  27. 13 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OAuth2TokenRevocationTests.java
  28. 1 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcClientRegistrationTests.java
  29. 12 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcProviderConfigurationTests.java
  30. 1 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcTests.java
  31. 1 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/config/annotation/web/configurers/OidcUserInfoTests.java
  32. 7 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java
  33. 28 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/settings/AuthorizationServerSettingsTests.java
  34. 7 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java
  35. 1 1
      samples/demo-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

+ 30 - 0
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();
+	}
+
+}

+ 8 - 5
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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.authorizationRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.authorizationRequestConverters);

+ 10 - 5
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<HttpSecurity> exceptionHandling = httpSecurity.getConfigurer(ExceptionHandlingConfigurer.class);
@@ -343,8 +345,11 @@ public final class OAuth2AuthorizationServerConfigurer
 
 		JWKSource<com.nimbusds.jose.proc.SecurityContext> 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);
 		}
 	}

+ 6 - 2
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

+ 17 - 13
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<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
 		if (!this.authenticationProviders.isEmpty()) {

+ 1 - 1
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 :

+ 9 - 5
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<AuthenticationProvider> 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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.deviceAuthorizationRequestConverters.isEmpty()) {

+ 8 - 5
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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.deviceVerificationRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.deviceVerificationRequestConverters);

+ 9 - 6
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<AuthenticationProvider> 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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.accessTokenRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.accessTokenRequestConverters);

+ 9 - 6
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<AuthenticationProvider> 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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.introspectionRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.introspectionRequestConverters);

+ 9 - 5
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<AuthenticationProvider> 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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.revocationRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.revocationRequestConverters);

+ 8 - 5
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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.clientRegistrationRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);

+ 8 - 5
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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.logoutRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.logoutRequestConverters);

+ 6 - 2
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

+ 8 - 5
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<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
 		if (!this.userInfoRequestConverters.isEmpty()) {
 			authenticationConverters.addAll(0, this.userInfoRequestConverters);

+ 1 - 1
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 <i>"current"</i> request.
 	 *
 	 * <p>
-	 * 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.
 	 *
 	 * <p>
 	 * For example:

+ 13 - 4
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<OidcProviderConfiguration.Builder> 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<List<String>> clientAuthenticationMethods() {
 		return (authenticationMethods) -> {
 			authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());

+ 53 - 2
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.
+	 *
+	 * <p>
+	 * For example:
+	 * <ul>
+	 * <li>{@code https://example.com/issuer1}</li>
+	 * <li>{@code https://example.com/authz/issuer2}</li>
+	 * </ul>
+	 *
+	 * @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.
+		 *
+		 * <p>
+		 * For example:
+		 * <ul>
+		 * <li>{@code https://example.com/issuer1}</li>
+		 * <li>{@code https://example.com/authz/issuer2}</li>
+		 * </ul>
+		 *
+		 * <p>
+		 * <b>NOTE:</b> 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 <i>"current"</i> 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;
 		}
 
 	}

+ 6 - 0
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.
 		 */

+ 13 - 4
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<OAuth2AuthorizationServerMetadata.Builder> 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<List<String>> clientAuthenticationMethods() {
 		return (authenticationMethods) -> {
 			authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());

+ 4 - 3
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<SecurityContext> 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();
 		}
 	}
 

+ 12 - 1
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();
+		}
+
+	}
+
 }

+ 3 - 3
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();
 		}
 	}
 

+ 13 - 1
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();
+		}
+
+	}
+
 }

+ 14 - 2
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();
+		}
+
+	}
+
 }

+ 16 - 9
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<OAuth2TokenClaimsContext> accessTokenCustomizer;
 	private static AuthenticationConverter authenticationConverter;
 	private static Consumer<List<AuthenticationConverter>> 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();
+		}
+
 	}
 
 }

+ 13 - 1
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();
+		}
+
+	}
+
 }

+ 1 - 0
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();
 		}
 

+ 12 - 2
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

+ 1 - 1
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

+ 1 - 0
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();
 		}
 

+ 7 - 0
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);

+ 28 - 2
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.<String>getSetting("name1")).isEqualTo("value1");
 		assertThat(authorizationServerSettings.<String>getSetting("name2")).isEqualTo("value2");
 	}

+ 7 - 0
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);

+ 1 - 1
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);