Преглед на файлове

Support resolving issuer from current request

Closes gh-479
Joe Grandja преди 3 години
родител
ревизия
666d569b48
променени са 34 файла, в които са добавени 468 реда и са изтрити 150 реда
  1. 9 7
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 6 11
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java
  3. 2 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java
  4. 20 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java
  5. 38 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java
  6. 2 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java
  7. 18 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java
  8. 2 5
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java
  9. 21 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java
  10. 13 9
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java
  11. 38 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java
  12. 8 2
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java
  13. 15 10
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java
  14. 27 6
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java
  15. 37 0
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/WebAttributes.java
  16. 4 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java
  17. 5 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java
  18. 5 1
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java
  19. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java
  20. 2 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JwtEncodingContextTests.java
  21. 15 14
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java
  22. 13 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java
  23. 14 8
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java
  24. 13 3
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java
  25. 26 13
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java
  26. 13 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java
  27. 32 17
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java
  28. 16 6
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java
  29. 9 2
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java
  30. 3 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java
  31. 32 0
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java
  32. 8 1
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java
  33. 0 6
      samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java
  34. 0 6
      samples/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

+ 9 - 7
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@@ -216,9 +216,17 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 
 	@Override
 	public void configure(B builder) {
+		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
+
+		// IMPORTANT:
+		// This filter must be registered first as it resolves the current issuer identifier and
+		// sets it as a request attribute under WebAttributes.ISSUER, which may be used by upstream components.
+		OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
+				new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
+		builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
+
 		this.configurers.values().forEach(configurer -> configurer.configure(builder));
 
-		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
 		AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
 
 		OAuth2TokenIntrospectionEndpointFilter tokenIntrospectionEndpointFilter =
@@ -238,12 +246,6 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
 						OAuth2ConfigurerUtils.getJwkSource(builder),
 						providerSettings.getJwkSetEndpoint());
 		builder.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
-
-		if (providerSettings.getIssuer() != null) {
-			OAuth2AuthorizationServerMetadataEndpointFilter authorizationServerMetadataEndpointFilter =
-					new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
-			builder.addFilterBefore(postProcess(authorizationServerMetadataEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
-		}
 	}
 
 	private Map<Class<? extends AbstractOAuth2Configurer>, AbstractOAuth2Configurer> createConfigurers() {

+ 6 - 11
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcConfigurer.java

@@ -85,16 +85,13 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
 		}
 
 		List<RequestMatcher> requestMatchers = new ArrayList<>();
-		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
-		if (providerSettings.getIssuer() != null) {
-			requestMatchers.add(new AntPathRequestMatcher(
-					"/.well-known/openid-configuration", HttpMethod.GET.name()));
-		}
+		requestMatchers.add(new AntPathRequestMatcher(
+				"/.well-known/openid-configuration", HttpMethod.GET.name()));
 		requestMatchers.add(this.userInfoEndpointConfigurer.getRequestMatcher());
 		if (this.clientRegistrationEndpointConfigurer != null) {
 			requestMatchers.add(this.clientRegistrationEndpointConfigurer.getRequestMatcher());
 		}
-		this.requestMatcher = requestMatchers.size() > 1 ? new OrRequestMatcher(requestMatchers) : requestMatchers.get(0);
+		this.requestMatcher = new OrRequestMatcher(requestMatchers);
 	}
 
 	@Override
@@ -105,11 +102,9 @@ public final class OidcConfigurer extends AbstractOAuth2Configurer {
 		}
 
 		ProviderSettings providerSettings = OAuth2ConfigurerUtils.getProviderSettings(builder);
-		if (providerSettings.getIssuer() != null) {
-			OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
-					new OidcProviderConfigurationEndpointFilter(providerSettings);
-			builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
-		}
+		OidcProviderConfigurationEndpointFilter oidcProviderConfigurationEndpointFilter =
+				new OidcProviderConfigurationEndpointFilter(providerSettings);
+		builder.addFilterBefore(postProcess(oidcProviderConfigurationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
 	}
 
 	@Override

+ 2 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

@@ -26,7 +26,6 @@ import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -87,7 +86,6 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
 	private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
-	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the provided parameters.
@@ -124,9 +122,8 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
 		this.refreshTokenGenerator = refreshTokenGenerator;
 	}
 
-	@Autowired(required = false)
+	@Deprecated
 	protected void setProviderSettings(ProviderSettings providerSettings) {
-		this.providerSettings = providerSettings;
 	}
 
 	@Override
@@ -167,7 +164,7 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
 		}
 
-		String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null;
+		String issuer = authorizationCodeAuthentication.getIssuer();
 		Set<String> authorizedScopes = authorization.getAttribute(
 				OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME);
 

+ 20 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationToken.java

@@ -43,7 +43,9 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2Authorizat
 	 * @param clientPrincipal the authenticated client principal
 	 * @param redirectUri the redirect uri
 	 * @param additionalParameters the additional parameters
+	 * @deprecated Use {@link #OAuth2AuthorizationCodeAuthenticationToken(String, String, Authentication, String, Map)} instead
 	 */
+	@Deprecated
 	public OAuth2AuthorizationCodeAuthenticationToken(String code, Authentication clientPrincipal,
 			@Nullable String redirectUri, @Nullable Map<String, Object> additionalParameters) {
 		super(AuthorizationGrantType.AUTHORIZATION_CODE, clientPrincipal, additionalParameters);
@@ -52,6 +54,24 @@ public class OAuth2AuthorizationCodeAuthenticationToken extends OAuth2Authorizat
 		this.redirectUri = redirectUri;
 	}
 
+	/**
+	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationToken} using the provided parameters.
+	 *
+	 * @param issuer the issuer identifier
+	 * @param code the authorization code
+	 * @param clientPrincipal the authenticated client principal
+	 * @param redirectUri the redirect uri
+	 * @param additionalParameters the additional parameters
+	 * @since 0.2.1
+	 */
+	public OAuth2AuthorizationCodeAuthenticationToken(String issuer, String code, Authentication clientPrincipal,
+			@Nullable String redirectUri, @Nullable Map<String, Object> additionalParameters) {
+		super(AuthorizationGrantType.AUTHORIZATION_CODE, issuer, clientPrincipal, additionalParameters);
+		Assert.hasText(code, "code cannot be empty");
+		this.code = code;
+		this.redirectUri = redirectUri;
+	}
+
 	/**
 	 * Returns the authorization code.
 	 *

+ 38 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationGrantAuthenticationToken.java

@@ -39,6 +39,7 @@ import org.springframework.util.Assert;
 public class OAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthenticationToken {
 	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
 	private final AuthorizationGrantType authorizationGrantType;
+	private final String issuer;
 	private final Authentication clientPrincipal;
 	private final Map<String, Object> additionalParameters;
 
@@ -48,13 +49,40 @@ public class OAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthent
 	 * @param authorizationGrantType the authorization grant type
 	 * @param clientPrincipal the authenticated client principal
 	 * @param additionalParameters the additional parameters
+	 * @deprecated Use {@link #OAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType, String, Authentication, Map)} instead
 	 */
+	@Deprecated
 	protected OAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType,
 			Authentication clientPrincipal, @Nullable Map<String, Object> additionalParameters) {
 		super(Collections.emptyList());
 		Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
 		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
 		this.authorizationGrantType = authorizationGrantType;
+		this.issuer = null;
+		this.clientPrincipal = clientPrincipal;
+		this.additionalParameters = Collections.unmodifiableMap(
+				additionalParameters != null ?
+						new HashMap<>(additionalParameters) :
+						Collections.emptyMap());
+	}
+
+	/**
+	 * Sub-class constructor.
+	 *
+	 * @param authorizationGrantType the authorization grant type
+	 * @param issuer the issuer identifier
+	 * @param clientPrincipal the authenticated client principal
+	 * @param additionalParameters the additional parameters
+	 * @since 0.2.1
+	 */
+	protected OAuth2AuthorizationGrantAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			String issuer, Authentication clientPrincipal, @Nullable Map<String, Object> additionalParameters) {
+		super(Collections.emptyList());
+		Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
+		Assert.hasText(issuer, "issuer cannot be empty");
+		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
+		this.authorizationGrantType = authorizationGrantType;
+		this.issuer = issuer;
 		this.clientPrincipal = clientPrincipal;
 		this.additionalParameters = Collections.unmodifiableMap(
 				additionalParameters != null ?
@@ -71,6 +99,16 @@ public class OAuth2AuthorizationGrantAuthenticationToken extends AbstractAuthent
 		return this.authorizationGrantType;
 	}
 
+	/**
+	 * Returns the issuer identifier.
+	 *
+	 * @return the issuer identifier
+	 * @since 0.2.1
+	 */
+	public String getIssuer() {
+		return this.issuer;
+	}
+
 	@Override
 	public Object getPrincipal() {
 		return this.clientPrincipal;

+ 2 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProvider.java

@@ -19,7 +19,6 @@ import java.util.LinkedHashSet;
 import java.util.Set;
 import java.util.function.Consumer;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -62,7 +61,6 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
 	private final OAuth2AuthorizationService authorizationService;
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
-	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the provided parameters.
@@ -90,9 +88,8 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
 		this.jwtCustomizer = jwtCustomizer;
 	}
 
-	@Autowired(required = false)
+	@Deprecated
 	protected void setProviderSettings(ProviderSettings providerSettings) {
-		this.providerSettings = providerSettings;
 	}
 
 	@Override
@@ -118,7 +115,7 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
 			authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
 		}
 
-		String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null;
+		String issuer = clientCredentialsAuthentication.getIssuer();
 
 		JoseHeader.Builder headersBuilder = JwtUtils.headers();
 		JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(

+ 18 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationToken.java

@@ -41,7 +41,9 @@ public class OAuth2ClientCredentialsAuthenticationToken extends OAuth2Authorizat
 	 * @param clientPrincipal the authenticated client principal
 	 * @param scopes the requested scope(s)
 	 * @param additionalParameters the additional parameters
+	 * @deprecated Use {@link #OAuth2ClientCredentialsAuthenticationToken(String, Authentication, Set, Map)} instead
 	 */
+	@Deprecated
 	public OAuth2ClientCredentialsAuthenticationToken(Authentication clientPrincipal,
 			@Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
 		super(AuthorizationGrantType.CLIENT_CREDENTIALS, clientPrincipal, additionalParameters);
@@ -49,6 +51,22 @@ public class OAuth2ClientCredentialsAuthenticationToken extends OAuth2Authorizat
 				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
 	}
 
+	/**
+	 * Constructs an {@code OAuth2ClientCredentialsAuthenticationToken} using the provided parameters.
+	 *
+	 * @param issuer the issuer identifier
+	 * @param clientPrincipal the authenticated client principal
+	 * @param scopes the requested scope(s)
+	 * @param additionalParameters the additional parameters
+	 * @since 0.2.1
+	 */
+	public OAuth2ClientCredentialsAuthenticationToken(String issuer, Authentication clientPrincipal,
+			@Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
+		super(AuthorizationGrantType.CLIENT_CREDENTIALS, issuer, clientPrincipal, additionalParameters);
+		this.scopes = Collections.unmodifiableSet(
+				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
+	}
+
 	/**
 	 * Returns the requested scope(s).
 	 *

+ 2 - 5
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProvider.java

@@ -26,7 +26,6 @@ import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationProvider;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
@@ -80,7 +79,6 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
 	private final JwtEncoder jwtEncoder;
 	private OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = (context) -> {};
 	private Supplier<String> refreshTokenGenerator = DEFAULT_REFRESH_TOKEN_GENERATOR::generateKey;
-	private ProviderSettings providerSettings;
 
 	/**
 	 * Constructs an {@code OAuth2RefreshTokenAuthenticationProvider} using the provided parameters.
@@ -118,9 +116,8 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
 		this.refreshTokenGenerator = refreshTokenGenerator;
 	}
 
-	@Autowired(required = false)
+	@Deprecated
 	protected void setProviderSettings(ProviderSettings providerSettings) {
-		this.providerSettings = providerSettings;
 	}
 
 	@Override
@@ -166,7 +163,7 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
 			scopes = authorizedScopes;
 		}
 
-		String issuer = this.providerSettings != null ? this.providerSettings.getIssuer() : null;
+		String issuer = refreshTokenAuthentication.getIssuer();
 
 		JoseHeader.Builder headersBuilder = JwtUtils.headers();
 		JwtClaimsSet.Builder claimsBuilder = JwtUtils.accessTokenClaims(

+ 21 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationToken.java

@@ -44,7 +44,9 @@ public class OAuth2RefreshTokenAuthenticationToken extends OAuth2AuthorizationGr
 	 * @param clientPrincipal the authenticated client principal
 	 * @param scopes the requested scope(s)
 	 * @param additionalParameters the additional parameters
+	 * @deprecated Use {@link #OAuth2RefreshTokenAuthenticationToken(String, String, Authentication, Set, Map)} instead
 	 */
+	@Deprecated
 	public OAuth2RefreshTokenAuthenticationToken(String refreshToken, Authentication clientPrincipal,
 			@Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
 		super(AuthorizationGrantType.REFRESH_TOKEN, clientPrincipal, additionalParameters);
@@ -54,6 +56,25 @@ public class OAuth2RefreshTokenAuthenticationToken extends OAuth2AuthorizationGr
 				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
 	}
 
+	/**
+	 * Constructs an {@code OAuth2RefreshTokenAuthenticationToken} using the provided parameters.
+	 *
+	 * @param issuer the issuer identifier
+	 * @param refreshToken the refresh token
+	 * @param clientPrincipal the authenticated client principal
+	 * @param scopes the requested scope(s)
+	 * @param additionalParameters the additional parameters
+	 * @since 0.2.1
+	 */
+	public OAuth2RefreshTokenAuthenticationToken(String issuer, String refreshToken, Authentication clientPrincipal,
+			@Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
+		super(AuthorizationGrantType.REFRESH_TOKEN, issuer, clientPrincipal, additionalParameters);
+		Assert.hasText(refreshToken, "refreshToken cannot be empty");
+		this.refreshToken = refreshToken;
+		this.scopes = Collections.unmodifiableSet(
+				scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
+	}
+
 	/**
 	 * Returns the refresh token.
 	 *

+ 13 - 9
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java

@@ -178,9 +178,11 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
 		}
 
-		OidcClientRegistration clientRegistration = buildRegistration(registeredClient).build();
+		OidcClientRegistration clientRegistration = buildRegistration(
+				registeredClient, clientRegistrationAuthentication.getIssuer())
+				.build();
 
-		return new OidcClientRegistrationAuthenticationToken(
+		return new OidcClientRegistrationAuthenticationToken(clientRegistrationAuthentication.getIssuer(),
 				(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
 	}
 
@@ -198,7 +200,8 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		RegisteredClient registeredClient = createClient(clientRegistrationAuthentication.getClientRegistration());
 		this.registeredClientRepository.save(registeredClient);
 
-		OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient);
+		OAuth2Authorization registeredClientAuthorization = registerAccessToken(
+				registeredClient, clientRegistrationAuthentication.getIssuer());
 
 		// Invalidate the "initial" access token as it can only be used once
 		authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorizedAccessToken.getToken());
@@ -207,21 +210,22 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		}
 		this.authorizationService.save(authorization);
 
-		OidcClientRegistration clientRegistration = buildRegistration(registeredClient)
+		OidcClientRegistration clientRegistration = buildRegistration(
+				registeredClient, clientRegistrationAuthentication.getIssuer())
 				.registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue())
 				.build();
 
-		return new OidcClientRegistrationAuthenticationToken(
+		return new OidcClientRegistrationAuthenticationToken(clientRegistrationAuthentication.getIssuer(),
 				(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
 	}
 
-	private OAuth2Authorization registerAccessToken(RegisteredClient registeredClient) {
+	private OAuth2Authorization registerAccessToken(RegisteredClient registeredClient, String issuer) {
 		JoseHeader headers = JwtUtils.headers().build();
 
 		Set<String> authorizedScopes = Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
 
 		JwtClaimsSet claims = JwtUtils.accessTokenClaims(
-				registeredClient, this.providerSettings.getIssuer(), registeredClient.getClientId(), authorizedScopes)
+				registeredClient, issuer, registeredClient.getClientId(), authorizedScopes)
 				.build();
 
 		Jwt registrationAccessToken = this.jwtEncoder.encode(headers, claims);
@@ -246,7 +250,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 		return registeredClientAuthorization;
 	}
 
-	private OidcClientRegistration.Builder buildRegistration(RegisteredClient registeredClient) {
+	private OidcClientRegistration.Builder buildRegistration(RegisteredClient registeredClient, String issuer) {
 		// @formatter:off
 		OidcClientRegistration.Builder builder = OidcClientRegistration.builder()
 				.clientId(registeredClient.getClientId())
@@ -270,7 +274,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
 					scopes.addAll(registeredClient.getScopes()));
 		}
 
-		String registrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer())
+		String registrationClientUri = UriComponentsBuilder.fromUriString(issuer)
 				.path(this.providerSettings.getOidcClientRegistrationEndpoint())
 				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
 				.toUriString();

+ 38 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java

@@ -36,6 +36,7 @@ import org.springframework.util.Assert;
  */
 public class OidcClientRegistrationAuthenticationToken extends AbstractAuthenticationToken {
 	private static final long serialVersionUID = Version.SERIAL_VERSION_UID;
+	private final String issuer;
 	private final Authentication principal;
 	private final OidcClientRegistration clientRegistration;
 	private final String clientId;
@@ -45,11 +46,14 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic
 	 *
 	 * @param principal the authenticated principal
 	 * @param clientRegistration the client registration
+	 * @deprecated Use {@link #OidcClientRegistrationAuthenticationToken(String, Authentication, OidcClientRegistration)} instead
 	 */
+	@Deprecated
 	public OidcClientRegistrationAuthenticationToken(Authentication principal, OidcClientRegistration clientRegistration) {
 		super(Collections.emptyList());
 		Assert.notNull(principal, "principal cannot be null");
 		Assert.notNull(clientRegistration, "clientRegistration cannot be null");
+		this.issuer = null;
 		this.principal = principal;
 		this.clientRegistration = clientRegistration;
 		this.clientId = null;
@@ -59,20 +63,53 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic
 	/**
 	 * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided parameters.
 	 *
+	 * @param issuer the issuer identifier
+	 * @param principal the authenticated principal
+	 * @param clientRegistration the client registration
+	 * @since 0.2.1
+	 */
+	public OidcClientRegistrationAuthenticationToken(String issuer, Authentication principal, OidcClientRegistration clientRegistration) {
+		super(Collections.emptyList());
+		Assert.hasText(issuer, "issuer cannot be empty");
+		Assert.notNull(principal, "principal cannot be null");
+		Assert.notNull(clientRegistration, "clientRegistration cannot be null");
+		this.issuer = issuer;
+		this.principal = principal;
+		this.clientRegistration = clientRegistration;
+		this.clientId = null;
+		setAuthenticated(principal.isAuthenticated());
+	}
+
+	/**
+	 * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided parameters.
+	 *
+	 * @param issuer the issuer identifier
 	 * @param principal the authenticated principal
 	 * @param clientId the client identifier
 	 * @since 0.2.1
 	 */
-	public OidcClientRegistrationAuthenticationToken(Authentication principal, String clientId) {
+	public OidcClientRegistrationAuthenticationToken(String issuer, Authentication principal, String clientId) {
 		super(Collections.emptyList());
+		Assert.hasText(issuer, "issuer cannot be empty");
 		Assert.notNull(principal, "principal cannot be null");
 		Assert.hasText(clientId, "clientId cannot be empty");
+		this.issuer = issuer;
 		this.principal = principal;
 		this.clientRegistration = null;
 		this.clientId = clientId;
 		setAuthenticated(principal.isAuthenticated());
 	}
 
+	/**
+	 * Returns the issuer identifier.
+	 *
+	 * @return the issuer identifier
+	 * @since 0.2.1
+	 */
+	public String getIssuer() {
+		return this.issuer;
+	}
+
 	@Override
 	public Object getPrincipal() {
 		return this.principal;

+ 8 - 2
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java

@@ -38,6 +38,7 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMe
 import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
 import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.OrRequestMatcher;
@@ -148,7 +149,10 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 		if ("POST".equals(request.getMethod())) {
 			OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read(
 					OidcClientRegistration.class, new ServletServerHttpRequest(request));
-			return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration);
+
+			String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
+			return new OidcClientRegistrationAuthenticationToken(issuer, principal, clientRegistration);
 		}
 
 		// client_id (REQUIRED)
@@ -158,7 +162,9 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi
 			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
 		}
 
-		return new OidcClientRegistrationAuthenticationToken(principal, clientId);
+		String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
+		return new OidcClientRegistrationAuthenticationToken(issuer, principal, clientId);
 	}
 
 	private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException {

+ 15 - 10
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilter.java

@@ -15,6 +15,13 @@
  */
 package org.springframework.security.oauth2.server.authorization.oidc.web;
 
+import java.io.IOException;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.http.server.ServletServerHttpResponse;
@@ -26,18 +33,13 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
 import org.springframework.security.oauth2.core.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter;
 import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.util.UriComponentsBuilder;
 
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
 /**
  * A {@code Filter} that processes OpenID Provider Configuration Requests.
  *
@@ -76,13 +78,15 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
 			return;
 		}
 
+		String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
 		OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.builder()
-				.issuer(this.providerSettings.getIssuer())
-				.authorizationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getAuthorizationEndpoint()))
-				.tokenEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint()))
+				.issuer(issuer)
+				.authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint()))
+				.tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint()))
 				.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
 				.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue())
-				.jwkSetUrl(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getJwkSetEndpoint()))
+				.jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint()))
 				.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
 				.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
 				.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
@@ -100,4 +104,5 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
 	private static String asUrl(String issuer, String endpoint) {
 		return UriComponentsBuilder.fromUriString(issuer).path(endpoint).build().toUriString();
 	}
+
 }

+ 27 - 6
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilter.java

@@ -33,6 +33,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthorizationServerMetadat
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
 import org.springframework.security.oauth2.core.http.converter.OAuth2AuthorizationServerMetadataHttpMessageConverter;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.web.util.UrlUtils;
 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 import org.springframework.util.Assert;
@@ -43,6 +44,7 @@ import org.springframework.web.util.UriComponentsBuilder;
  * A {@code Filter} that processes OAuth 2.0 Authorization Server Metadata Requests.
  *
  * @author Daniel Garnier-Moiroux
+ * @author Joe Grandja
  * @since 0.1.1
  * @see OAuth2AuthorizationServerMetadata
  * @see ProviderSettings
@@ -72,24 +74,32 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 			throws ServletException, IOException {
 
+		// Resolve the current issuer identifier
+		String issuer = this.providerSettings.getIssuer();
+		if (issuer == null) {
+			issuer = resolveIssuer(request);
+		}
+		// Set the current issuer identifier as a request attribute (for use by upstream components)
+		request.setAttribute(WebAttributes.ISSUER, issuer);
+
 		if (!this.requestMatcher.matches(request)) {
 			filterChain.doFilter(request, response);
 			return;
 		}
 
 		OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
-				.issuer(this.providerSettings.getIssuer())
-				.authorizationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getAuthorizationEndpoint()))
-				.tokenEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenEndpoint()))
+				.issuer(issuer)
+				.authorizationEndpoint(asUrl(issuer, this.providerSettings.getAuthorizationEndpoint()))
+				.tokenEndpoint(asUrl(issuer, this.providerSettings.getTokenEndpoint()))
 				.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
-				.jwkSetUrl(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getJwkSetEndpoint()))
+				.jwkSetUrl(asUrl(issuer, this.providerSettings.getJwkSetEndpoint()))
 				.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
 				.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
 				.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
 				.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
-				.tokenRevocationEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenRevocationEndpoint()))
+				.tokenRevocationEndpoint(asUrl(issuer, this.providerSettings.getTokenRevocationEndpoint()))
 				.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
-				.tokenIntrospectionEndpoint(asUrl(this.providerSettings.getIssuer(), this.providerSettings.getTokenIntrospectionEndpoint()))
+				.tokenIntrospectionEndpoint(asUrl(issuer, this.providerSettings.getTokenIntrospectionEndpoint()))
 				.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
 				.codeChallengeMethod("plain")
 				.codeChallengeMethod("S256")
@@ -100,6 +110,17 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
 				authorizationServerMetadata, MediaType.APPLICATION_JSON, httpResponse);
 	}
 
+	private static String resolveIssuer(HttpServletRequest request) {
+		// @formatter:off
+		return UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
+				.replacePath(request.getContextPath())
+				.replaceQuery(null)
+				.fragment(null)
+				.build()
+				.toUriString();
+		// @formatter:on
+	}
+
 	private static Consumer<List<String>> clientAuthenticationMethods() {
 		return (authenticationMethods) -> {
 			authenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue());

+ 37 - 0
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/WebAttributes.java

@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020-2021 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 org.springframework.security.oauth2.server.authorization.web;
+
+import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+
+/**
+ * Well-known attribute names which are used to store information in request or session scope.
+ *
+ * @author Joe Grandja
+ * @since 0.2.1
+ */
+public final class WebAttributes {
+
+	private WebAttributes() {
+	}
+
+	/**
+	 * The {@link javax.servlet.http.HttpServletRequest#getAttribute(String) request attribute} name that holds the current issuer identifier.
+	 * The issuer identifier is resolved from {@link ProviderSettings#getIssuer()} or dynamically from the current {@link javax.servlet.http.HttpServletRequest}.
+	 */
+	public static final String ISSUER = WebAttributes.class.getName().concat(".ISSUER");
+
+}

+ 4 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2AuthorizationCodeAuthenticationConverter.java

@@ -28,6 +28,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
@@ -88,8 +89,10 @@ public final class OAuth2AuthorizationCodeAuthenticationConverter implements Aut
 			}
 		});
 
+		String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
 		return new OAuth2AuthorizationCodeAuthenticationToken(
-				code, clientPrincipal, redirectUri, additionalParameters);
+				issuer, code, clientPrincipal, redirectUri, additionalParameters);
 	}
 
 }

+ 5 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2ClientCredentialsAuthenticationConverter.java

@@ -31,6 +31,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
@@ -83,7 +84,10 @@ public final class OAuth2ClientCredentialsAuthenticationConverter implements Aut
 			}
 		});
 
+		String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
 		return new OAuth2ClientCredentialsAuthenticationToken(
-				clientPrincipal, requestedScopes, additionalParameters);
+				issuer, clientPrincipal, requestedScopes, additionalParameters);
 	}
+
 }

+ 5 - 1
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/OAuth2RefreshTokenAuthenticationConverter.java

@@ -31,6 +31,7 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
 import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationToken;
 import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
@@ -94,7 +95,10 @@ public final class OAuth2RefreshTokenAuthenticationConverter implements Authenti
 			}
 		});
 
+		String issuer = (String) request.getAttribute(WebAttributes.ISSUER);
+
 		return new OAuth2RefreshTokenAuthenticationToken(
-				refreshToken, clientPrincipal, requestedScopes, additionalParameters);
+				issuer, refreshToken, clientPrincipal, requestedScopes, additionalParameters);
 	}
+
 }

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java

@@ -213,10 +213,11 @@ public class OAuth2ClientCredentialsGrantTests {
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		this.registeredClientRepository.save(registeredClient);
 
+		String issuer = "https://example.com/issuer1";
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, null, null);
 		when(authenticationConverter.convert(any())).thenReturn(clientCredentialsAuthentication);
 
 		OAuth2AccessToken accessToken = new OAuth2AccessToken(

+ 2 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/JwtEncodingContextTests.java

@@ -88,9 +88,10 @@ public class JwtEncodingContextTests {
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
+		String issuer = "https://provider.com";
 		OAuth2AuthorizationCodeAuthenticationToken authorizationGrant =
 				new OAuth2AuthorizationCodeAuthenticationToken(
-						"code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
+						issuer, "code", clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		JwtEncodingContext context = JwtEncodingContext.with(headers, claims)
 				.registeredClient(registeredClient)

+ 15 - 14
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationProviderTests.java

@@ -34,6 +34,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.core.AuthorizationGrantType;
 import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
 import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
 import org.springframework.security.oauth2.core.OAuth2TokenType;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -48,7 +49,6 @@ import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
 import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
-import org.springframework.security.oauth2.core.OAuth2AuthorizationCode;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
 import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
 import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
@@ -74,6 +74,7 @@ import static org.mockito.Mockito.when;
  * @author Daniel Garnier-Moiroux
  */
 public class OAuth2AuthorizationCodeAuthenticationProviderTests {
+	private static final String ISSUER = "https://example.com/issuer1";
 	private static final String AUTHORIZATION_CODE = "code";
 	private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
 	private OAuth2AuthorizationService authorizationService;
@@ -130,7 +131,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
 				registeredClient.getClientId(), registeredClient.getClientSecret());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, null, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -144,7 +145,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null);
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, null, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -158,7 +159,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, null, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -176,7 +177,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, null, null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, null, null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -203,7 +204,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri() + "-invalid", null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri() + "-invalid", null);
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
 				.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
@@ -227,7 +228,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -253,7 +254,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -274,7 +275,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
 
@@ -330,7 +331,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
 
@@ -404,7 +405,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
 
@@ -467,7 +468,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		Instant accessTokenIssuedAt = Instant.now();
 		Instant accessTokenExpiresAt = accessTokenIssuedAt.plus(accessTokenTTL);
@@ -506,7 +507,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
 
@@ -539,7 +540,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
 		OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
 				OAuth2AuthorizationRequest.class.getName());
 		OAuth2AuthorizationCodeAuthenticationToken authentication =
-				new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
+				new OAuth2AuthorizationCodeAuthenticationToken(ISSUER, AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);

+ 13 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2AuthorizationCodeAuthenticationTokenTests.java

@@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
  * @author Daniel Garnier-Moiroux
  */
 public class OAuth2AuthorizationCodeAuthenticationTokenTests {
+	private String issuer = "https://example.com/issuer1";
 	private String code = "code";
 	private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 	private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
@@ -42,16 +43,23 @@ public class OAuth2AuthorizationCodeAuthenticationTokenTests {
 	private String redirectUri = "redirectUri";
 	private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
 
+	@Test
+	public void constructorWhenIssuerNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.code, this.clientPrincipal, this.redirectUri, null))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("issuer cannot be empty");
+	}
+
 	@Test
 	public void constructorWhenCodeNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(null, this.clientPrincipal, this.redirectUri, null))
+		assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.issuer, null, this.clientPrincipal, this.redirectUri, null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("code cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.code, null, this.redirectUri, null))
+		assertThatThrownBy(() -> new OAuth2AuthorizationCodeAuthenticationToken(this.issuer, this.code, null, this.redirectUri, null))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientPrincipal cannot be null");
 	}
@@ -59,8 +67,9 @@ public class OAuth2AuthorizationCodeAuthenticationTokenTests {
 	@Test
 	public void constructorWhenClientPrincipalProvidedThenCreated() {
 		OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
-				this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
+				this.issuer, this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
 		assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE);
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();
 		assertThat(authentication.getCode()).isEqualTo(this.code);
@@ -71,7 +80,7 @@ public class OAuth2AuthorizationCodeAuthenticationTokenTests {
 	@Test
 	public void getAdditionalParametersWhenUpdateThenThrowUnsupportedOperationException() {
 		OAuth2AuthorizationCodeAuthenticationToken authentication = new OAuth2AuthorizationCodeAuthenticationToken(
-				this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
+				this.issuer, this.code, this.clientPrincipal, this.redirectUri, this.additionalParameters);
 		assertThatThrownBy(() -> authentication.getAdditionalParameters().put("another_key", 1))
 				.isInstanceOf(UnsupportedOperationException.class);
 		assertThatThrownBy(() -> authentication.getAdditionalParameters().remove("some_key"))

+ 14 - 8
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationProviderTests.java

@@ -36,12 +36,12 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
 import org.springframework.security.oauth2.jwt.JoseHeaderNames;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtEncoder;
+import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
 import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
 import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
-import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
-import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -105,11 +105,12 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
 				registeredClient.getClientId(), registeredClient.getClientSecret());
 		OAuth2ClientCredentialsAuthenticationToken authentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -120,11 +121,12 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null);
 		OAuth2ClientCredentialsAuthenticationToken authentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -135,13 +137,14 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientNotAuthorizedToRequestTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2()
 				.authorizationGrantTypes(grantTypes -> grantTypes.remove(AuthorizationGrantType.CLIENT_CREDENTIALS))
 				.build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2ClientCredentialsAuthenticationToken authentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -152,11 +155,12 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenInvalidScopeThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
-				clientPrincipal, Collections.singleton("invalid-scope"), null);
+				issuer, clientPrincipal, Collections.singleton("invalid-scope"), null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -167,12 +171,13 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		Set<String> requestedScope = Collections.singleton("scope1");
 		OAuth2ClientCredentialsAuthenticationToken authentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, requestedScope, null);
 
 		when(this.jwtEncoder.encode(any(), any()))
 				.thenReturn(createJwt(Collections.singleton("mapped-scoped")));
@@ -184,11 +189,12 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenValidAuthenticationThenReturnAccessToken() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2ClientCredentialsAuthenticationToken authentication =
-				new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
+				new OAuth2ClientCredentialsAuthenticationToken(issuer, clientPrincipal, null, null);
 
 		when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(registeredClient.getScopes()));
 

+ 13 - 3
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2ClientCredentialsAuthenticationTokenTests.java

@@ -35,15 +35,23 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
  * @author Alexey Nesterov
  */
 public class OAuth2ClientCredentialsAuthenticationTokenTests {
+	private String issuer = "https://example.com/issuer1";
 	private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 	private final OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 			this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
 	private Set<String> scopes = Collections.singleton("scope1");
 	private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
 
+	@Test
+	public void constructorWhenIssuerNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> new OAuth2ClientCredentialsAuthenticationToken(null, this.clientPrincipal, this.scopes, this.additionalParameters))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("issuer cannot be empty");
+	}
+
 	@Test
 	public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2ClientCredentialsAuthenticationToken(null, this.scopes, this.additionalParameters))
+		assertThatThrownBy(() -> new OAuth2ClientCredentialsAuthenticationToken(this.issuer, null, this.scopes, this.additionalParameters))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientPrincipal cannot be null");
 	}
@@ -51,9 +59,10 @@ public class OAuth2ClientCredentialsAuthenticationTokenTests {
 	@Test
 	public void constructorWhenClientPrincipalProvidedThenCreated() {
 		OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
-				this.clientPrincipal, this.scopes, this.additionalParameters);
+				this.issuer, this.clientPrincipal, this.scopes, this.additionalParameters);
 
 		assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();
 		assertThat(authentication.getScopes()).isEqualTo(this.scopes);
@@ -65,9 +74,10 @@ public class OAuth2ClientCredentialsAuthenticationTokenTests {
 		Set<String> expectedScopes = Collections.singleton("test-scope");
 
 		OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
-				this.clientPrincipal, expectedScopes, this.additionalParameters);
+				this.issuer, this.clientPrincipal, expectedScopes, this.additionalParameters);
 
 		assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();
 		assertThat(authentication.getScopes()).isEqualTo(expectedScopes);

+ 26 - 13
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationProviderTests.java

@@ -132,6 +132,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenValidRefreshTokenThenReturnAccessToken() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
 		when(this.authorizationService.findByToken(
@@ -142,7 +143,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -176,6 +177,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenValidRefreshTokenThenReturnIdToken() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
 		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
 		when(this.authorizationService.findByToken(
@@ -186,7 +188,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -243,6 +245,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenReuseRefreshTokensFalseThenReturnNewRefreshToken() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
 				.tokenSettings(TokenSettings.builder().reuseRefreshTokens(false).build())
 				.build();
@@ -255,7 +258,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -270,6 +273,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenRequestedScopesAuthorizedThenAccessTokenIncludesScopes() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
 				.scope("scope2")
 				.scope("scope3")
@@ -286,7 +290,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		Set<String> requestedScopes = new HashSet<>(authorizedScopes);
 		requestedScopes.remove("scope1");
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -296,6 +300,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenCustomRefreshTokenGeneratorThenUsed() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
 				.tokenSettings(TokenSettings.builder().reuseRefreshTokens(false).build())
 				.build();
@@ -317,7 +322,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
 				(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -328,6 +333,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenRequestedScopesNotAuthorizedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
 		when(this.authorizationService.findByToken(
@@ -341,7 +347,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		Set<String> requestedScopes = new HashSet<>(authorizedScopes);
 		requestedScopes.add("unauthorized");
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, requestedScopes, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -352,11 +358,12 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenInvalidRefreshTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				"invalid", clientPrincipal, null, null);
+				issuer, "invalid", clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -367,11 +374,12 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientPrincipalNotOAuth2ClientAuthenticationTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		TestingAuthenticationToken clientPrincipal = new TestingAuthenticationToken(
 				registeredClient.getClientId(), registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				"refresh-token", clientPrincipal, null, null);
+				issuer, "refresh-token", clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -382,11 +390,12 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient.getClientId(), ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret(), null);
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				"refresh-token", clientPrincipal, null, null);
+				issuer, "refresh-token", clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -397,6 +406,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenRefreshTokenIssuedToAnotherClientThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
 		when(this.authorizationService.findByToken(
@@ -408,7 +418,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient2, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient2.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -419,6 +429,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientNotAuthorizedToRefreshTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
 				.authorizationGrantTypes(grantTypes -> grantTypes.remove(AuthorizationGrantType.REFRESH_TOKEN))
 				.build();
@@ -431,7 +442,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -442,6 +453,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenExpiredRefreshTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
 		OAuth2RefreshToken expiredRefreshToken = new OAuth2RefreshToken(
@@ -455,7 +467,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -466,6 +478,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenRevokedRefreshTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
 				"refresh-token", Instant.now().minusSeconds(120), Instant.now().plusSeconds(1000));
@@ -480,7 +493,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
 		OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
+				issuer, authorization.getRefreshToken().getToken().getTokenValue(), clientPrincipal, null, null);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)

+ 13 - 4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/authentication/OAuth2RefreshTokenAuthenticationTokenTests.java

@@ -36,25 +36,33 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
  * @since 0.0.3
  */
 public class OAuth2RefreshTokenAuthenticationTokenTests {
+	private String issuer = "https://example.com/issuer1";
 	private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 	private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
 			this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
 	private Set<String> scopes = Collections.singleton("scope1");
 	private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
 
+	@Test
+	public void constructorWhenIssuerNullThenThrowIllegalArgumentException() {
+		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, "refresh-token", this.clientPrincipal, this.scopes, this.additionalParameters))
+				.isInstanceOf(IllegalArgumentException.class)
+				.hasMessage("issuer cannot be empty");
+	}
+
 	@Test
 	public void constructorWhenRefreshTokenNullOrEmptyThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(null, this.clientPrincipal, this.scopes, this.additionalParameters))
+		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(this.issuer, null, this.clientPrincipal, this.scopes, this.additionalParameters))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("refreshToken cannot be empty");
-		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("", this.clientPrincipal, this.scopes, this.additionalParameters))
+		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(this.issuer, "", this.clientPrincipal, this.scopes, this.additionalParameters))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("refreshToken cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenClientPrincipalNullThenThrowIllegalArgumentException() {
-		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken("refresh-token", null, this.scopes, this.additionalParameters))
+		assertThatThrownBy(() -> new OAuth2RefreshTokenAuthenticationToken(this.issuer, "refresh-token", null, this.scopes, this.additionalParameters))
 				.isInstanceOf(IllegalArgumentException.class)
 				.hasMessage("clientPrincipal cannot be null");
 	}
@@ -62,8 +70,9 @@ public class OAuth2RefreshTokenAuthenticationTokenTests {
 	@Test
 	public void constructorWhenScopesProvidedThenCreated() {
 		OAuth2RefreshTokenAuthenticationToken authentication = new OAuth2RefreshTokenAuthenticationToken(
-				"refresh-token", this.clientPrincipal, this.scopes, this.additionalParameters);
+				this.issuer, "refresh-token", this.clientPrincipal, this.scopes, this.additionalParameters);
 		assertThat(authentication.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getRefreshToken()).isEqualTo("refresh-token");
 		assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();

+ 32 - 17
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java

@@ -83,7 +83,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		this.registeredClientRepository = mock(RegisteredClientRepository.class);
 		this.authorizationService = mock(OAuth2AuthorizationService.class);
 		this.jwtEncoder = mock(JwtEncoder.class);
-		this.providerSettings = ProviderSettings.builder().issuer("https://auth-server:9000").build();
+		this.providerSettings = ProviderSettings.builder().build();
 		this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider(
 				this.registeredClientRepository, this.authorizationService, this.jwtEncoder);
 		this.authenticationProvider.setProviderSettings(this.providerSettings);
@@ -117,13 +117,14 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
 		OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
 				.redirectUri("https://client.example.com")
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -133,13 +134,14 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientRegistration());
 		OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
 				.redirectUri("https://client.example.com")
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -149,6 +151,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientRegistration();
 		JwtAuthenticationToken principal = new JwtAuthenticationToken(
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -157,7 +160,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -169,6 +172,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -188,7 +192,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -200,6 +204,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientRegistrationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -218,7 +223,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -230,6 +235,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientRegistrationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.create", "scope1")));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -248,7 +254,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				.build();
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -260,6 +266,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientRegistrationRequestAndInvalidRedirectUriThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -280,7 +287,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		// @formatter:on
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -292,6 +299,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientRegistrationRequestAndRedirectUriContainsFragmentThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -312,7 +320,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		// @formatter:on
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -324,6 +332,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientRegistrationRequestAndValidAccessTokenThenReturnClientRegistration() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientRegistration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -350,7 +359,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		// @formatter:on
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, clientRegistration);
+				issuer, principal, clientRegistration);
+
 		OidcClientRegistrationAuthenticationToken authenticationResult =
 				(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
 
@@ -415,7 +425,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
 				.isEqualTo(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
 
-		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer())
+		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(issuer)
 				.path(this.providerSettings.getOidcClientRegistrationEndpoint())
 				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClientResult.getClientId()).toUriString();
 
@@ -425,6 +435,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt(Collections.singleton("unauthorized.scope"));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -440,7 +451,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
+				issuer, principal, registeredClient.getClientId());
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -452,6 +463,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientConfigurationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1")));
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -467,7 +479,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1"));
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
+				issuer, principal, registeredClient.getClientId());
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -479,6 +491,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientConfiguration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -494,7 +507,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
+				issuer, principal, registeredClient.getClientId());
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -508,6 +521,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientConfigurationRequestClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientConfiguration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -527,7 +541,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
+				issuer, principal, registeredClient.getClientId());
 
 		assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
 				.isInstanceOf(OAuth2AuthenticationException.class)
@@ -541,6 +555,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 
 	@Test
 	public void authenticateWhenClientConfigurationRequestAndValidAccessTokenThenReturnClientRegistration() {
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwtClientConfiguration();
 		OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
 				jwt.getTokenValue(), jwt.getIssuedAt(),
@@ -560,7 +575,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
 
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				principal, registeredClient.getClientId());
+				issuer, principal, registeredClient.getClientId());
 
 		OidcClientRegistrationAuthenticationToken authenticationResult =
 				(OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -597,7 +612,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
 		assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm())
 				.isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName());
 
-		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer())
+		String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(issuer)
 				.path(this.providerSettings.getOidcClientRegistrationEndpoint())
 				.queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString();
 

+ 16 - 6
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java

@@ -29,43 +29,52 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
  * @author Joe Grandja
  */
 public class OidcClientRegistrationAuthenticationTokenTests {
+	private String issuer = "https://example.com/issuer1";
 	private TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials");
 	private OidcClientRegistration clientRegistration = OidcClientRegistration.builder()
 			.redirectUri("https://client.example.com").build();
 
+	@Test
+	public void constructorWhenIssuerNullThenThrowIllegalArgumentException() {
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(null, this.principal, this.clientRegistration))
+				.withMessage("issuer cannot be empty");
+	}
+
 	@Test
 	public void constructorWhenPrincipalNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
-				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(null, this.clientRegistration))
+				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.issuer, null, this.clientRegistration))
 				.withMessage("principal cannot be null");
 	}
 
 	@Test
 	public void constructorWhenClientRegistrationNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
-				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (OidcClientRegistration) null))
+				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.issuer, this.principal, (OidcClientRegistration) null))
 				.withMessage("clientRegistration cannot be null");
 	}
 
 	@Test
 	public void constructorWhenClientIdNullThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
-				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (String) null))
+				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.issuer, this.principal, (String) null))
 				.withMessage("clientId cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenClientIdEmptyThenThrowIllegalArgumentException() {
 		assertThatIllegalArgumentException()
-				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, ""))
+				.isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.issuer, this.principal, ""))
 				.withMessage("clientId cannot be empty");
 	}
 
 	@Test
 	public void constructorWhenOidcClientRegistrationProvidedThenCreated() {
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				this.principal, this.clientRegistration);
+				this.issuer, this.principal, this.clientRegistration);
 
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();
 		assertThat(authentication.getClientRegistration()).isEqualTo(this.clientRegistration);
@@ -76,8 +85,9 @@ public class OidcClientRegistrationAuthenticationTokenTests {
 	@Test
 	public void constructorWhenClientIdProvidedThenCreated() {
 		OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
-				this.principal, "client-1");
+				this.issuer, this.principal, "client-1");
 
+		assertThat(authentication.getIssuer()).isEqualTo(this.issuer);
 		assertThat(authentication.getPrincipal()).isEqualTo(this.principal);
 		assertThat(authentication.getCredentials().toString()).isEmpty();
 		assertThat(authentication.getClientRegistration()).isNull();

+ 9 - 2
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java

@@ -53,6 +53,7 @@ import org.springframework.security.oauth2.jwt.JwtClaimsSet;
 import org.springframework.security.oauth2.jwt.TestJoseHeaders;
 import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
 import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -188,6 +189,7 @@ public class OidcClientRegistrationEndpointFilterTests {
 		MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
 		request.setServletPath(requestUri);
 		writeClientRegistrationRequest(request, clientRegistrationRequest);
+		request.setAttribute(WebAttributes.ISSUER, "https://example.com/issuer1");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 
@@ -225,12 +227,13 @@ public class OidcClientRegistrationEndpointFilterTests {
 				.build();
 		// @formatter:on
 
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt("client.create");
 		JwtAuthenticationToken principal = new JwtAuthenticationToken(
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
 
 		OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult =
-				new OidcClientRegistrationAuthenticationToken(principal, expectedClientRegistrationResponse);
+				new OidcClientRegistrationAuthenticationToken(issuer, principal, expectedClientRegistrationResponse);
 
 		when(this.authenticationManager.authenticate(any())).thenReturn(clientRegistrationAuthenticationResult);
 
@@ -242,6 +245,7 @@ public class OidcClientRegistrationEndpointFilterTests {
 		MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
 		request.setServletPath(requestUri);
 		writeClientRegistrationRequest(request, clientRegistrationRequest);
+		request.setAttribute(WebAttributes.ISSUER, issuer);
 
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
@@ -370,6 +374,7 @@ public class OidcClientRegistrationEndpointFilterTests {
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
 		request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1");
+		request.setAttribute(WebAttributes.ISSUER, "https://example.com/issuer1");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 
@@ -402,12 +407,13 @@ public class OidcClientRegistrationEndpointFilterTests {
 				.build();
 		// @formatter:on
 
+		String issuer = "https://example.com/issuer1";
 		Jwt jwt = createJwt("client.read");
 		JwtAuthenticationToken principal = new JwtAuthenticationToken(
 				jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read"));
 
 		OidcClientRegistrationAuthenticationToken clientConfigurationAuthenticationResult =
-				new OidcClientRegistrationAuthenticationToken(principal, expectedClientRegistrationResponse);
+				new OidcClientRegistrationAuthenticationToken(issuer, principal, expectedClientRegistrationResponse);
 
 		when(this.authenticationManager.authenticate(any())).thenReturn(clientConfigurationAuthenticationResult);
 
@@ -419,6 +425,7 @@ public class OidcClientRegistrationEndpointFilterTests {
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
 		request.setParameter(OAuth2ParameterNames.CLIENT_ID, expectedClientRegistrationResponse.getClientId());
+		request.setAttribute(WebAttributes.ISSUER, issuer);
 
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);

+ 3 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcProviderConfigurationEndpointFilterTests.java

@@ -25,6 +25,7 @@ import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
 import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
+import org.springframework.security.oauth2.server.authorization.web.WebAttributes;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
@@ -98,6 +99,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
 		String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
+		request.setAttribute(WebAttributes.ISSUER, providerSettings.getIssuer());
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 
@@ -130,6 +132,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
 		String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
 		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
 		request.setServletPath(requestUri);
+		request.setAttribute(WebAttributes.ISSUER, providerSettings.getIssuer());
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		FilterChain filterChain = mock(FilterChain.class);
 

+ 32 - 0
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationServerMetadataEndpointFilterTests.java

@@ -146,4 +146,36 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
 				.withMessage("issuer must be a valid URL");
 	}
 
+	@Test
+	public void doFilterWhenProviderSettingsWithIssuerNotSetThenIssuerResolvesFromRequest() throws Exception {
+		ProviderSettings providerSettings = ProviderSettings.builder().build();
+		OAuth2AuthorizationServerMetadataEndpointFilter filter =
+				new OAuth2AuthorizationServerMetadataEndpointFilter(providerSettings);
+
+		String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI;
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
+		request.setServletPath(requestUri);
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		FilterChain filterChain = mock(FilterChain.class);
+
+		filter.doFilter(request, response, filterChain);
+
+		verifyNoInteractions(filterChain);
+
+		assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
+		String authorizationServerMetadataResponse = response.getContentAsString();
+		assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"http://localhost\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"authorization_endpoint\":\"http://localhost/oauth2/authorize\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint\":\"http://localhost/oauth2/token\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
+		assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"http://localhost/oauth2/jwks\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]");
+		assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
+		assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint\":\"http://localhost/oauth2/revoke\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
+		assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint\":\"http://localhost/oauth2/introspect\"");
+		assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
+		assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"plain\",\"S256\"]");
+	}
+
 }

+ 8 - 1
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilterTests.java

@@ -489,12 +489,13 @@ public class OAuth2TokenEndpointFilterTests {
 
 	@Test
 	public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
+		String issuer = "https://example.com/issuer1";
 		RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
 		Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
 				registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
 
 		OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
-				new OAuth2AuthorizationCodeAuthenticationToken("code", clientPrincipal, null, null);
+				new OAuth2AuthorizationCodeAuthenticationToken(issuer, "code", clientPrincipal, null, null);
 
 		AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
 		when(authenticationConverter.convert(any())).thenReturn(authorizationCodeAuthentication);
@@ -613,6 +614,8 @@ public class OAuth2TokenEndpointFilterTests {
 		request.addParameter(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
 		request.addParameter("custom-param-1", "custom-value-1");
 
+		request.setAttribute(WebAttributes.ISSUER, "https://example.com/issuer1");
+
 		return request;
 	}
 
@@ -627,6 +630,8 @@ public class OAuth2TokenEndpointFilterTests {
 				StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
 		request.addParameter("custom-param-1", "custom-value-1");
 
+		request.setAttribute(WebAttributes.ISSUER, "https://example.com/issuer1");
+
 		return request;
 	}
 
@@ -642,6 +647,8 @@ public class OAuth2TokenEndpointFilterTests {
 				StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
 		request.addParameter("custom-param-1", "custom-value-1");
 
+		request.setAttribute(WebAttributes.ISSUER, "https://example.com/issuer1");
+
 		return request;
 	}
 }

+ 0 - 6
samples/custom-consent-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -39,7 +39,6 @@ import org.springframework.security.oauth2.server.authorization.client.InMemoryR
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.util.matcher.RequestMatcher;
 
@@ -101,11 +100,6 @@ public class AuthorizationServerConfig {
 		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
-	@Bean
-	public ProviderSettings providerSettings() {
-		return ProviderSettings.builder().issuer("http://auth-server:9000").build();
-	}
-
 	@Bean
 	public OAuth2AuthorizationConsentService authorizationConsentService() {
 		// Will be used by the ConsentController

+ 0 - 6
samples/default-authorizationserver/src/main/java/sample/config/AuthorizationServerConfig.java

@@ -45,7 +45,6 @@ import org.springframework.security.oauth2.server.authorization.client.JdbcRegis
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
 import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
 import org.springframework.security.oauth2.server.authorization.config.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
 import org.springframework.security.web.SecurityFilterChain;
 
 /**
@@ -105,11 +104,6 @@ public class AuthorizationServerConfig {
 		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
 	}
 
-	@Bean
-	public ProviderSettings providerSettings() {
-		return ProviderSettings.builder().issuer("http://auth-server:9000").build();
-	}
-
 	@Bean
 	public EmbeddedDatabase embeddedDatabase() {
 		// @formatter:off