Browse Source

Simplify OAuth2 Client configuration

Issue gh-11783
Steve Riesenberg 2 years ago
parent
commit
5828e4e65c

+ 208 - 133
config/src/main/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfiguration.java

@@ -16,22 +16,25 @@
 
 
 package org.springframework.security.config.annotation.web.configuration;
 package org.springframework.security.config.annotation.web.configuration;
 
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 
-import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.BeanFactoryAware;
 import org.springframework.beans.factory.BeanFactoryAware;
 import org.springframework.beans.factory.BeanFactoryUtils;
 import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.BeanInitializationException;
 import org.springframework.beans.factory.ListableBeanFactory;
 import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
 import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
 import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
-import org.springframework.beans.factory.support.ManagedList;
 import org.springframework.context.annotation.AnnotationBeanNameGenerator;
 import org.springframework.context.annotation.AnnotationBeanNameGenerator;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
@@ -43,11 +46,12 @@ import org.springframework.security.core.context.SecurityContextHolderStrategy;
 import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
 import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
@@ -112,16 +116,12 @@ final class OAuth2ClientConfiguration {
 	@Configuration(proxyBeanMethods = false)
 	@Configuration(proxyBeanMethods = false)
 	static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer {
 	static class OAuth2ClientWebMvcSecurityConfiguration implements WebMvcConfigurer {
 
 
-		private ClientRegistrationRepository clientRegistrationRepository;
-
-		private OAuth2AuthorizedClientRepository authorizedClientRepository;
-
-		private OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient;
-
 		private OAuth2AuthorizedClientManager authorizedClientManager;
 		private OAuth2AuthorizedClientManager authorizedClientManager;
 
 
 		private SecurityContextHolderStrategy securityContextHolderStrategy;
 		private SecurityContextHolderStrategy securityContextHolderStrategy;
 
 
+		private OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar;
+
 		@Override
 		@Override
 		public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
 		public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
 			OAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
 			OAuth2AuthorizedClientManager authorizedClientManager = getAuthorizedClientManager();
@@ -135,26 +135,6 @@ final class OAuth2ClientConfiguration {
 			}
 			}
 		}
 		}
 
 
-		@Autowired(required = false)
-		void setClientRegistrationRepository(List<ClientRegistrationRepository> clientRegistrationRepositories) {
-			if (clientRegistrationRepositories.size() == 1) {
-				this.clientRegistrationRepository = clientRegistrationRepositories.get(0);
-			}
-		}
-
-		@Autowired(required = false)
-		void setAuthorizedClientRepository(List<OAuth2AuthorizedClientRepository> authorizedClientRepositories) {
-			if (authorizedClientRepositories.size() == 1) {
-				this.authorizedClientRepository = authorizedClientRepositories.get(0);
-			}
-		}
-
-		@Autowired(required = false)
-		void setAccessTokenResponseClient(
-				OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient) {
-			this.accessTokenResponseClient = accessTokenResponseClient;
-		}
-
 		@Autowired(required = false)
 		@Autowired(required = false)
 		void setAuthorizedClientManager(List<OAuth2AuthorizedClientManager> authorizedClientManagers) {
 		void setAuthorizedClientManager(List<OAuth2AuthorizedClientManager> authorizedClientManagers) {
 			if (authorizedClientManagers.size() == 1) {
 			if (authorizedClientManagers.size() == 1) {
@@ -167,33 +147,17 @@ final class OAuth2ClientConfiguration {
 			this.securityContextHolderStrategy = strategy;
 			this.securityContextHolderStrategy = strategy;
 		}
 		}
 
 
+		@Autowired
+		void setAuthorizedClientManagerRegistrar(
+				OAuth2AuthorizedClientManagerRegistrar authorizedClientManagerRegistrar) {
+			this.authorizedClientManagerRegistrar = authorizedClientManagerRegistrar;
+		}
+
 		private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
 		private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
 			if (this.authorizedClientManager != null) {
 			if (this.authorizedClientManager != null) {
 				return this.authorizedClientManager;
 				return this.authorizedClientManager;
 			}
 			}
-			OAuth2AuthorizedClientManager authorizedClientManager = null;
-			if (this.clientRegistrationRepository != null && this.authorizedClientRepository != null) {
-				if (this.accessTokenResponseClient != null) {
-					// @formatter:off
-					OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
-						.builder()
-						.authorizationCode()
-						.refreshToken()
-						.clientCredentials((configurer) -> configurer.accessTokenResponseClient(this.accessTokenResponseClient))
-						.password()
-						.build();
-					// @formatter:on
-					DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
-							this.clientRegistrationRepository, this.authorizedClientRepository);
-					defaultAuthorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
-					authorizedClientManager = defaultAuthorizedClientManager;
-				}
-				else {
-					authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
-							this.clientRegistrationRepository, this.authorizedClientRepository);
-				}
-			}
-			return authorizedClientManager;
+			return this.authorizedClientManagerRegistrar.getAuthorizedClientManagerIfAvailable();
 		}
 		}
 
 
 	}
 	}
@@ -203,36 +167,37 @@ final class OAuth2ClientConfiguration {
 	 * definition, if not already present.
 	 * definition, if not already present.
 	 *
 	 *
 	 * @author Joe Grandja
 	 * @author Joe Grandja
+	 * @author Steve Riesenberg
 	 * @since 6.2.0
 	 * @since 6.2.0
 	 */
 	 */
-	static class OAuth2AuthorizedClientManagerRegistrar
+	static final class OAuth2AuthorizedClientManagerRegistrar
 			implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
 			implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
 
 
+		// @formatter:off
+		private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
+				AuthorizationCodeOAuth2AuthorizedClientProvider.class,
+				RefreshTokenOAuth2AuthorizedClientProvider.class,
+				ClientCredentialsOAuth2AuthorizedClientProvider.class,
+				PasswordOAuth2AuthorizedClientProvider.class,
+				JwtBearerOAuth2AuthorizedClientProvider.class
+		);
+		// @formatter:on
+
 		private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
 		private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
 
 
-		private BeanFactory beanFactory;
+		private ListableBeanFactory beanFactory;
 
 
 		@Override
 		@Override
 		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
 		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
-			String[] authorizedClientManagerBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, OAuth2AuthorizedClientManager.class, true, true);
-			if (authorizedClientManagerBeanNames.length != 0) {
-				return;
-			}
-
-			String[] clientRegistrationRepositoryBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, ClientRegistrationRepository.class, true, true);
-			String[] authorizedClientRepositoryBeanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
-			if (clientRegistrationRepositoryBeanNames.length != 1 || authorizedClientRepositoryBeanNames.length != 1) {
+			if (getBeanNamesForType(OAuth2AuthorizedClientManager.class).length != 0
+					|| getBeanNamesForType(ClientRegistrationRepository.class).length != 1
+					|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
 				return;
 				return;
 			}
 			}
 
 
 			BeanDefinition beanDefinition = BeanDefinitionBuilder
 			BeanDefinition beanDefinition = BeanDefinitionBuilder
-					.genericBeanDefinition(DefaultOAuth2AuthorizedClientManager.class)
-					.addConstructorArgReference(clientRegistrationRepositoryBeanNames[0])
-					.addConstructorArgReference(authorizedClientRepositoryBeanNames[0])
-					.addPropertyValue("authorizedClientProvider", getAuthorizedClientProvider()).getBeanDefinition();
+					.genericBeanDefinition(OAuth2AuthorizedClientManager.class, this::getAuthorizedClientManager)
+					.getBeanDefinition();
 
 
 			registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
 			registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
 					beanDefinition);
 					beanDefinition);
@@ -242,90 +207,200 @@ final class OAuth2ClientConfiguration {
 		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
 		}
 		}
 
 
-		private BeanDefinition getAuthorizedClientProvider() {
-			ManagedList<Object> authorizedClientProviders = new ManagedList<>();
-			authorizedClientProviders.add(getAuthorizationCodeAuthorizedClientProvider());
-			authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider());
-			authorizedClientProviders.add(getClientCredentialsAuthorizedClientProvider());
-			authorizedClientProviders.add(getPasswordAuthorizedClientProvider());
-			return BeanDefinitionBuilder.genericBeanDefinition(DelegatingOAuth2AuthorizedClientProvider.class)
-					.addConstructorArgValue(authorizedClientProviders).getBeanDefinition();
+		@Override
+		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+			this.beanFactory = (ListableBeanFactory) beanFactory;
 		}
 		}
 
 
-		private BeanMetadataElement getAuthorizationCodeAuthorizedClientProvider() {
-			String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, AuthorizationCodeOAuth2AuthorizedClientProvider.class, true,
-					true);
-			if (beanNames.length == 1) {
-				return new RuntimeBeanReference(beanNames[0]);
+		OAuth2AuthorizedClientManager getAuthorizedClientManagerIfAvailable() {
+			if (getBeanNamesForType(ClientRegistrationRepository.class).length != 1
+					|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
+				return null;
 			}
 			}
+			return getAuthorizedClientManager();
+		}
 
 
-			return BeanDefinitionBuilder.genericBeanDefinition(AuthorizationCodeOAuth2AuthorizedClientProvider.class)
-					.getBeanDefinition();
+		private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
+			ClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
+					.beanOfTypeIncludingAncestors(this.beanFactory, ClientRegistrationRepository.class, true, true);
+
+			OAuth2AuthorizedClientRepository authorizedClientRepository = BeanFactoryUtils
+					.beanOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
+
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
+					.beansOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientProvider.class, true, true)
+					.values();
+
+			OAuth2AuthorizedClientProvider authorizedClientProvider;
+			if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
+				authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
+			}
+			else {
+				List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
+				authorizedClientProviders
+						.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
+				authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
+				authorizedClientProviders
+						.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
+				authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
+
+				OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
+						authorizedClientProviderBeans);
+				if (jwtBearerAuthorizedClientProvider != null) {
+					authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
+				}
+
+				authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
+				authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
+			}
+
+			DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
+					clientRegistrationRepository, authorizedClientRepository);
+			authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+			Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
+					ResolvableType.forClassWithGenerics(Consumer.class, DefaultOAuth2AuthorizedClientManager.class));
+			if (authorizedClientManagerConsumer != null) {
+				authorizedClientManagerConsumer.accept(authorizedClientManager);
+			}
+
+			return authorizedClientManager;
 		}
 		}
 
 
-		private BeanMetadataElement getRefreshTokenAuthorizedClientProvider() {
-			String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, RefreshTokenOAuth2AuthorizedClientProvider.class, true,
-					true);
-			if (beanNames.length == 1) {
-				return new RuntimeBeanReference(beanNames[0]);
+		private boolean hasDelegatingAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			if (authorizedClientProviders.size() != 1) {
+				return false;
 			}
 			}
+			return authorizedClientProviders.iterator().next() instanceof DelegatingOAuth2AuthorizedClientProvider;
+		}
 
 
-			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
-					.genericBeanDefinition(RefreshTokenOAuth2AuthorizedClientProvider.class);
-			ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
-					OAuth2RefreshTokenGrantRequest.class);
-			beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
-					resolvableType, true, true);
-			if (beanNames.length == 1) {
-				beanDefinitionBuilder.addPropertyReference("accessTokenResponseClient", beanNames[0]);
+		private OAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			AuthorizationCodeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+					authorizedClientProviders, AuthorizationCodeOAuth2AuthorizedClientProvider.class);
+			if (authorizedClientProvider == null) {
+				authorizedClientProvider = new AuthorizationCodeOAuth2AuthorizedClientProvider();
 			}
 			}
-			return beanDefinitionBuilder.getBeanDefinition();
+
+			return authorizedClientProvider;
 		}
 		}
 
 
-		private BeanMetadataElement getClientCredentialsAuthorizedClientProvider() {
-			String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, ClientCredentialsOAuth2AuthorizedClientProvider.class, true,
-					true);
-			if (beanNames.length == 1) {
-				return new RuntimeBeanReference(beanNames[0]);
+		private OAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+					authorizedClientProviders, RefreshTokenOAuth2AuthorizedClientProvider.class);
+			if (authorizedClientProvider == null) {
+				authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
 			}
 			}
 
 
-			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
-					.genericBeanDefinition(ClientCredentialsOAuth2AuthorizedClientProvider.class);
-			ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
-					OAuth2ClientCredentialsGrantRequest.class);
-			beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
-					resolvableType, true, true);
-			if (beanNames.length == 1) {
-				beanDefinitionBuilder.addPropertyReference("accessTokenResponseClient", beanNames[0]);
+			OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
+					ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+							OAuth2RefreshTokenGrantRequest.class));
+			if (accessTokenResponseClient != null) {
+				authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
 			}
 			}
-			return beanDefinitionBuilder.getBeanDefinition();
+
+			return authorizedClientProvider;
 		}
 		}
 
 
-		private BeanMetadataElement getPasswordAuthorizedClientProvider() {
-			String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
-					(ListableBeanFactory) this.beanFactory, PasswordOAuth2AuthorizedClientProvider.class, true, true);
-			if (beanNames.length == 1) {
-				return new RuntimeBeanReference(beanNames[0]);
+		private OAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+					authorizedClientProviders, ClientCredentialsOAuth2AuthorizedClientProvider.class);
+			if (authorizedClientProvider == null) {
+				authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
 			}
 			}
 
 
-			BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
-					.genericBeanDefinition(PasswordOAuth2AuthorizedClientProvider.class);
-			ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
-					OAuth2PasswordGrantRequest.class);
-			beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors((ListableBeanFactory) this.beanFactory,
-					resolvableType, true, true);
-			if (beanNames.length == 1) {
-				beanDefinitionBuilder.addPropertyReference("accessTokenResponseClient", beanNames[0]);
+			OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
+					ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+							OAuth2ClientCredentialsGrantRequest.class));
+			if (accessTokenResponseClient != null) {
+				authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
 			}
 			}
-			return beanDefinitionBuilder.getBeanDefinition();
+
+			return authorizedClientProvider;
 		}
 		}
 
 
-		@Override
-		public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
-			this.beanFactory = beanFactory;
+		private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+					authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
+			if (authorizedClientProvider == null) {
+				authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
+			}
+
+			OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
+					ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+							OAuth2PasswordGrantRequest.class));
+			if (accessTokenResponseClient != null) {
+				authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+			}
+
+			return authorizedClientProvider;
+		}
+
+		private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+					authorizedClientProviders, JwtBearerOAuth2AuthorizedClientProvider.class);
+
+			OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(
+					ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+							JwtBearerGrantRequest.class));
+			if (accessTokenResponseClient != null) {
+				if (authorizedClientProvider == null) {
+					authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
+				}
+
+				authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+			}
+
+			return authorizedClientProvider;
+		}
+
+		private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+			List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
+					authorizedClientProviders);
+			additionalAuthorizedClientProviders
+					.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
+			return additionalAuthorizedClientProviders;
+		}
+
+		private <T extends OAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
+				Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
+			T authorizedClientProvider = null;
+			for (OAuth2AuthorizedClientProvider current : authorizedClientProviders) {
+				if (providerClass.isInstance(current)) {
+					assertAuthorizedClientProviderIsNull(authorizedClientProvider);
+					authorizedClientProvider = providerClass.cast(current);
+				}
+			}
+			return authorizedClientProvider;
+		}
+
+		private static void assertAuthorizedClientProviderIsNull(
+				OAuth2AuthorizedClientProvider authorizedClientProvider) {
+			if (authorizedClientProvider != null) {
+				// @formatter:off
+				throw new BeanInitializationException(String.format(
+						"Unable to create an %s bean. Expected one bean of type %s, but found multiple. " +
+						"Please consider defining only a single bean of this type, or define an %s bean yourself.",
+						OAuth2AuthorizedClientManager.class.getName(),
+						authorizedClientProvider.getClass().getName(),
+						OAuth2AuthorizedClientManager.class.getName()));
+				// @formatter:on
+			}
+		}
+
+		private <T> String[] getBeanNamesForType(Class<T> beanClass) {
+			return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, true, true);
+		}
+
+		private <T> T getBeanOfType(ResolvableType resolvableType) {
+			ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
+			return objectProvider.getIfAvailable();
 		}
 		}
 
 
 	}
 	}

+ 18 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java

@@ -16,6 +16,8 @@
 
 
 package org.springframework.security.config.annotation.web.configurers.oauth2.client;
 package org.springframework.security.config.annotation.web.configurers.oauth2.client;
 
 
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.ResolvableType;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
@@ -307,7 +309,22 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 			if (this.accessTokenResponseClient != null) {
 			if (this.accessTokenResponseClient != null) {
 				return this.accessTokenResponseClient;
 				return this.accessTokenResponseClient;
 			}
 			}
-			return new DefaultAuthorizationCodeTokenResponseClient();
+			ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+					OAuth2AuthorizationCodeGrantRequest.class);
+			OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
+			return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
+		}
+
+		@SuppressWarnings("unchecked")
+		private <T> T getBeanOrNull(ResolvableType type) {
+			ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
+			if (context != null) {
+				String[] names = context.getBeanNamesForType(type);
+				if (names.length == 1) {
+					return (T) context.getBean(names[0]);
+				}
+			}
+			return null;
 		}
 		}
 
 
 	}
 	}

+ 11 - 4
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

@@ -330,10 +330,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 				super.init(http);
 				super.init(http);
 			}
 			}
 		}
 		}
-		OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = this.tokenEndpointConfig.accessTokenResponseClient;
-		if (accessTokenResponseClient == null) {
-			accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
-		}
+		OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient = getAccessTokenResponseClient();
 		OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
 		OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = getOAuth2UserService();
 		OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
 		OAuth2LoginAuthenticationProvider oauth2LoginAuthenticationProvider = new OAuth2LoginAuthenticationProvider(
 				accessTokenResponseClient, oauth2UserService);
 				accessTokenResponseClient, oauth2UserService);
@@ -441,6 +438,16 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		return (!grantedAuthoritiesMapperMap.isEmpty() ? grantedAuthoritiesMapperMap.values().iterator().next() : null);
 		return (!grantedAuthoritiesMapperMap.isEmpty() ? grantedAuthoritiesMapperMap.values().iterator().next() : null);
 	}
 	}
 
 
+	private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> getAccessTokenResponseClient() {
+		if (this.tokenEndpointConfig.accessTokenResponseClient != null) {
+			return this.tokenEndpointConfig.accessTokenResponseClient;
+		}
+		ResolvableType resolvableType = ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+				OAuth2AuthorizationCodeGrantRequest.class);
+		OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> bean = getBeanOrNull(resolvableType);
+		return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
+	}
+
 	private OAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
 	private OAuth2UserService<OidcUserRequest, OidcUser> getOidcUserService() {
 		if (this.userInfoEndpointConfig.oidcUserService != null) {
 		if (this.userInfoEndpointConfig.oidcUserService != null) {
 			return this.userInfoEndpointConfig.oidcUserService;
 			return this.userInfoEndpointConfig.oidcUserService;

+ 3 - 1
config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -420,6 +420,8 @@ final class AuthenticationConfigBuilder {
 			this.pc.getReaderContext()
 			this.pc.getReaderContext()
 					.registerWithGeneratedName(new RootBeanDefinition(OAuth2ClientWebMvcSecurityPostProcessor.class));
 					.registerWithGeneratedName(new RootBeanDefinition(OAuth2ClientWebMvcSecurityPostProcessor.class));
 		}
 		}
+		this.pc.getReaderContext()
+				.registerWithGeneratedName(new RootBeanDefinition(OAuth2AuthorizedClientManagerRegistrar.class));
 	}
 	}
 
 
 	private void createSaml2LoginFilter(BeanReference authenticationManager,
 	private void createSaml2LoginFilter(BeanReference authenticationManager,

+ 287 - 0
config/src/main/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrar.java

@@ -0,0 +1,287 @@
+/*
+ * Copyright 2002-2023 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.config.http;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.BeanInitializationException;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.BeanDefinitionRegistry;
+import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
+import org.springframework.context.annotation.AnnotationBeanNameGenerator;
+import org.springframework.core.ResolvableType;
+import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.DelegatingOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
+import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+
+/**
+ * A registrar for registering the default {@link OAuth2AuthorizedClientManager} bean
+ * definition, if not already present.
+ * <p>
+ * Note: This class is a direct copy of
+ * {@link org.springframework.security.config.annotation.web.configuration.OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerRegistrar}.
+ *
+ * @author Joe Grandja
+ * @author Steve Riesenberg
+ * @since 6.2.0
+ */
+final class OAuth2AuthorizedClientManagerRegistrar implements BeanDefinitionRegistryPostProcessor, BeanFactoryAware {
+
+	// @formatter:off
+	private static final Set<Class<?>> KNOWN_AUTHORIZED_CLIENT_PROVIDERS = Set.of(
+			AuthorizationCodeOAuth2AuthorizedClientProvider.class,
+			RefreshTokenOAuth2AuthorizedClientProvider.class,
+			ClientCredentialsOAuth2AuthorizedClientProvider.class,
+			PasswordOAuth2AuthorizedClientProvider.class,
+			JwtBearerOAuth2AuthorizedClientProvider.class
+	);
+	// @formatter:on
+
+	private final AnnotationBeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();
+
+	private ListableBeanFactory beanFactory;
+
+	@Override
+	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
+		if (getBeanNamesForType(OAuth2AuthorizedClientManager.class).length != 0
+				|| getBeanNamesForType(ClientRegistrationRepository.class).length != 1
+				|| getBeanNamesForType(OAuth2AuthorizedClientRepository.class).length != 1) {
+			return;
+		}
+
+		BeanDefinition beanDefinition = BeanDefinitionBuilder
+				.genericBeanDefinition(OAuth2AuthorizedClientManager.class, this::getAuthorizedClientManager)
+				.getBeanDefinition();
+
+		registry.registerBeanDefinition(this.beanNameGenerator.generateBeanName(beanDefinition, registry),
+				beanDefinition);
+	}
+
+	@Override
+	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+	}
+
+	@Override
+	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+		this.beanFactory = (ListableBeanFactory) beanFactory;
+	}
+
+	private OAuth2AuthorizedClientManager getAuthorizedClientManager() {
+		ClientRegistrationRepository clientRegistrationRepository = BeanFactoryUtils
+				.beanOfTypeIncludingAncestors(this.beanFactory, ClientRegistrationRepository.class, true, true);
+
+		OAuth2AuthorizedClientRepository authorizedClientRepository = BeanFactoryUtils
+				.beanOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientRepository.class, true, true);
+
+		Collection<OAuth2AuthorizedClientProvider> authorizedClientProviderBeans = BeanFactoryUtils
+				.beansOfTypeIncludingAncestors(this.beanFactory, OAuth2AuthorizedClientProvider.class, true, true)
+				.values();
+
+		OAuth2AuthorizedClientProvider authorizedClientProvider;
+		if (hasDelegatingAuthorizedClientProvider(authorizedClientProviderBeans)) {
+			authorizedClientProvider = authorizedClientProviderBeans.iterator().next();
+		}
+		else {
+			List<OAuth2AuthorizedClientProvider> authorizedClientProviders = new ArrayList<>();
+			authorizedClientProviders.add(getAuthorizationCodeAuthorizedClientProvider(authorizedClientProviderBeans));
+			authorizedClientProviders.add(getRefreshTokenAuthorizedClientProvider(authorizedClientProviderBeans));
+			authorizedClientProviders.add(getClientCredentialsAuthorizedClientProvider(authorizedClientProviderBeans));
+			authorizedClientProviders.add(getPasswordAuthorizedClientProvider(authorizedClientProviderBeans));
+
+			OAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider = getJwtBearerAuthorizedClientProvider(
+					authorizedClientProviderBeans);
+			if (jwtBearerAuthorizedClientProvider != null) {
+				authorizedClientProviders.add(jwtBearerAuthorizedClientProvider);
+			}
+
+			authorizedClientProviders.addAll(getAdditionalAuthorizedClientProviders(authorizedClientProviderBeans));
+			authorizedClientProvider = new DelegatingOAuth2AuthorizedClientProvider(authorizedClientProviders);
+		}
+
+		DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
+				clientRegistrationRepository, authorizedClientRepository);
+		authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
+
+		Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer = getBeanOfType(
+				ResolvableType.forClassWithGenerics(Consumer.class, DefaultOAuth2AuthorizedClientManager.class));
+		if (authorizedClientManagerConsumer != null) {
+			authorizedClientManagerConsumer.accept(authorizedClientManager);
+		}
+
+		return authorizedClientManager;
+	}
+
+	private boolean hasDelegatingAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		if (authorizedClientProviders.size() != 1) {
+			return false;
+		}
+		return authorizedClientProviders.iterator().next() instanceof DelegatingOAuth2AuthorizedClientProvider;
+	}
+
+	private OAuth2AuthorizedClientProvider getAuthorizationCodeAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		AuthorizationCodeOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+				authorizedClientProviders, AuthorizationCodeOAuth2AuthorizedClientProvider.class);
+		if (authorizedClientProvider == null) {
+			authorizedClientProvider = new AuthorizationCodeOAuth2AuthorizedClientProvider();
+		}
+
+		return authorizedClientProvider;
+	}
+
+	private OAuth2AuthorizedClientProvider getRefreshTokenAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+				authorizedClientProviders, RefreshTokenOAuth2AuthorizedClientProvider.class);
+		if (authorizedClientProvider == null) {
+			authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
+		}
+
+		OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> accessTokenResponseClient = getBeanOfType(
+				ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+						OAuth2RefreshTokenGrantRequest.class));
+		if (accessTokenResponseClient != null) {
+			authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+		}
+
+		return authorizedClientProvider;
+	}
+
+	private OAuth2AuthorizedClientProvider getClientCredentialsAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+				authorizedClientProviders, ClientCredentialsOAuth2AuthorizedClientProvider.class);
+		if (authorizedClientProvider == null) {
+			authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
+		}
+
+		OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> accessTokenResponseClient = getBeanOfType(
+				ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+						OAuth2ClientCredentialsGrantRequest.class));
+		if (accessTokenResponseClient != null) {
+			authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+		}
+
+		return authorizedClientProvider;
+	}
+
+	private OAuth2AuthorizedClientProvider getPasswordAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+				authorizedClientProviders, PasswordOAuth2AuthorizedClientProvider.class);
+		if (authorizedClientProvider == null) {
+			authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
+		}
+
+		OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> accessTokenResponseClient = getBeanOfType(
+				ResolvableType.forClassWithGenerics(OAuth2AccessTokenResponseClient.class,
+						OAuth2PasswordGrantRequest.class));
+		if (accessTokenResponseClient != null) {
+			authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+		}
+
+		return authorizedClientProvider;
+	}
+
+	private OAuth2AuthorizedClientProvider getJwtBearerAuthorizedClientProvider(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = getAuthorizedClientProviderByType(
+				authorizedClientProviders, JwtBearerOAuth2AuthorizedClientProvider.class);
+
+		OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> accessTokenResponseClient = getBeanOfType(ResolvableType
+				.forClassWithGenerics(OAuth2AccessTokenResponseClient.class, JwtBearerGrantRequest.class));
+		if (accessTokenResponseClient != null) {
+			if (authorizedClientProvider == null) {
+				authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
+			}
+
+			authorizedClientProvider.setAccessTokenResponseClient(accessTokenResponseClient);
+		}
+
+		return authorizedClientProvider;
+	}
+
+	private List<OAuth2AuthorizedClientProvider> getAdditionalAuthorizedClientProviders(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders) {
+		List<OAuth2AuthorizedClientProvider> additionalAuthorizedClientProviders = new ArrayList<>(
+				authorizedClientProviders);
+		additionalAuthorizedClientProviders
+				.removeIf((provider) -> KNOWN_AUTHORIZED_CLIENT_PROVIDERS.contains(provider.getClass()));
+		return additionalAuthorizedClientProviders;
+	}
+
+	private <T extends OAuth2AuthorizedClientProvider> T getAuthorizedClientProviderByType(
+			Collection<OAuth2AuthorizedClientProvider> authorizedClientProviders, Class<T> providerClass) {
+		T authorizedClientProvider = null;
+		for (OAuth2AuthorizedClientProvider current : authorizedClientProviders) {
+			if (providerClass.isInstance(current)) {
+				assertAuthorizedClientProviderIsNull(authorizedClientProvider);
+				authorizedClientProvider = providerClass.cast(current);
+			}
+		}
+		return authorizedClientProvider;
+	}
+
+	private static void assertAuthorizedClientProviderIsNull(OAuth2AuthorizedClientProvider authorizedClientProvider) {
+		if (authorizedClientProvider != null) {
+			// @formatter:off
+			throw new BeanInitializationException(String.format(
+					"Unable to create an %s bean. Expected one bean of type %s, but found multiple. " +
+					"Please consider defining only a single bean of this type, or define an %s bean yourself.",
+					OAuth2AuthorizedClientManager.class.getName(),
+					authorizedClientProvider.getClass().getName(),
+					OAuth2AuthorizedClientManager.class.getName()));
+			// @formatter:on
+		}
+	}
+
+	private <T> String[] getBeanNamesForType(Class<T> beanClass) {
+		return BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, beanClass, false, false);
+	}
+
+	private <T> T getBeanOfType(ResolvableType resolvableType) {
+		ObjectProvider<T> objectProvider = this.beanFactory.getBeanProvider(resolvableType, true);
+		return objectProvider.getIfAvailable();
+	}
+
+}

+ 394 - 65
config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2AuthorizedClientManagerConfigurationTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -16,149 +16,374 @@
 
 
 package org.springframework.security.config.annotation.web.configuration;
 package org.springframework.security.config.annotation.web.configuration;
 
 
+import java.time.Duration;
+import java.time.Instant;
 import java.util.Arrays;
 import java.util.Arrays;
-
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
 
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
-import org.springframework.http.converter.FormHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
 import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
+import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
 import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
-import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
-import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient;
-import org.springframework.security.oauth2.client.endpoint.DefaultPasswordTokenResponseClient;
-import org.springframework.security.oauth2.client.endpoint.DefaultRefreshTokenTokenResponseClient;
+import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
-import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
 import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
 import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
 import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
 import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
 import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
-import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
 import org.springframework.security.oauth2.core.oidc.user.OidcUser;
 import org.springframework.security.oauth2.core.oidc.user.OidcUser;
 import org.springframework.security.oauth2.core.user.OAuth2User;
 import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.oauth2.jwt.JoseHeaderNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
 import org.springframework.security.web.SecurityFilterChain;
 import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.web.client.RestOperations;
-import org.springframework.web.client.RestTemplate;
+import org.springframework.util.StringUtils;
 
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 
 /**
 /**
  * Tests for {@link OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration}.
  * Tests for {@link OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration}.
  *
  *
  * @author Joe Grandja
  * @author Joe Grandja
+ * @author Steve Riesenberg
  */
  */
 public class OAuth2AuthorizedClientManagerConfigurationTests {
 public class OAuth2AuthorizedClientManagerConfigurationTests {
 
 
+	private static OAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
+
 	public final SpringTestContext spring = new SpringTestContext(this);
 	public final SpringTestContext spring = new SpringTestContext(this);
 
 
 	@Autowired
 	@Autowired
 	private OAuth2AuthorizedClientManager authorizedClientManager;
 	private OAuth2AuthorizedClientManager authorizedClientManager;
 
 
+	@Autowired
+	private ClientRegistrationRepository clientRegistrationRepository;
+
+	@Autowired
+	private OAuth2AuthorizedClientRepository authorizedClientRepository;
+
 	@Autowired(required = false)
 	@Autowired(required = false)
 	private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
 	private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
 
 
-	@Autowired(required = false)
-	private RefreshTokenOAuth2AuthorizedClientProvider refreshTokenAuthorizedClientProvider;
+	private MockHttpServletRequest request;
 
 
-	@Autowired(required = false)
-	private ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider;
+	private MockHttpServletResponse response;
 
 
-	@Autowired(required = false)
-	private PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider;
+	@BeforeEach
+	@SuppressWarnings("unchecked")
+	public void setUp() {
+		MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class);
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+	}
 
 
 	@Test
 	@Test
-	public void loadContextWhenCustomRestOperationsThenConfigured() {
-		this.spring.register(CustomRestOperationsConfig.class).autowire();
+	public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
+		this.spring.register(MinimalOAuth2ClientConfig.class).autowire();
 		assertThat(this.authorizedClientManager).isNotNull();
 		assertThat(this.authorizedClientManager).isNotNull();
 	}
 	}
 
 
 	@Test
 	@Test
-	public void loadContextWhenCustomAuthorizedClientProvidersThenConfigured() {
+	public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
 		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
 		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
-		assertThat(this.authorizedClientManager).isNotNull();
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId("google")
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
+				.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest))
+				.extracting(OAuth2AuthorizationException::getError)
+				.extracting(OAuth2Error::getErrorCode)
+				.isEqualTo("client_authorization_required");
+		// @formatter:on
+
+		verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
+	}
+
+	@Test
+	public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
+		this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
+		testRefreshTokenGrant();
+	}
+
+	@Test
+	public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
+		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
+		testRefreshTokenGrant();
+	}
+
+	private void testRefreshTokenGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+		OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
+				authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
+		this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request,
+				this.response);
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withAuthorizedClient(existingAuthorizedClient)
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2RefreshTokenGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
+		assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
+		assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
+	}
+
+	@Test
+	public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
+		this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
+		testClientCredentialsGrant();
+	}
+
+	@Test
+	public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
+		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
+		testClientCredentialsGrant();
+	}
+
+	private void testClientCredentialsGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2ClientCredentialsGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
+	}
+
+	@Test
+	public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
+		this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
+		testPasswordGrant();
+	}
+
+	@Test
+	public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
+		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
+		testPasswordGrant();
+	}
+
+	private void testPasswordGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
+		this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2PasswordGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
+		assertThat(grantRequest.getUsername()).isEqualTo("user");
+		assertThat(grantRequest.getPassword()).isEqualTo("password");
+	}
+
+	@Test
+	public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
+		this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
+		testJwtBearerGrant();
+	}
+
+	@Test
+	public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
+		this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
+		testJwtBearerGrant();
+	}
+
+	private void testJwtBearerGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse);
+
+		JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
+		assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
+	}
+
+	private static OAuth2AccessToken getExpiredAccessToken() {
+		Instant expiresAt = Instant.now().minusSeconds(60);
+		Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
+		return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
+				new HashSet<>(Arrays.asList("read", "write")));
+	}
+
+	private static Jwt getJwt() {
+		Instant issuedAt = Instant.now();
+		return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
+				Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
+				Collections.singletonMap(JwtClaimNames.SUB, "user"));
+	}
+
+	@Configuration
+	@EnableWebSecurity
+	static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig {
+
 	}
 	}
 
 
 	@Configuration
 	@Configuration
 	@EnableWebSecurity
 	@EnableWebSecurity
-	static class CustomRestOperationsConfig extends OAuth2ClientBaseConfig {
+	static class CustomAccessTokenResponseClientsConfig extends OAuth2ClientBaseConfig {
 
 
-		// TODO This needs to be autoconfigured in OAuth2LoginConfigurer and
-		// OAuth2ClientConfigurer
 		@Bean
 		@Bean
 		OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
 		OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
-			DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
-			tokenResponseClient.setRestOperations(restOperations());
-			return spy(tokenResponseClient);
+			return new MockAuthorizationCodeClient();
 		}
 		}
 
 
 		@Bean
 		@Bean
 		OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
 		OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
-			DefaultRefreshTokenTokenResponseClient tokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
-			tokenResponseClient.setRestOperations(restOperations());
-			return spy(tokenResponseClient);
+			return new MockRefreshTokenClient();
 		}
 		}
 
 
 		@Bean
 		@Bean
 		OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
 		OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
-			DefaultClientCredentialsTokenResponseClient tokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
-			tokenResponseClient.setRestOperations(restOperations());
-			return spy(tokenResponseClient);
+			return new MockClientCredentialsClient();
 		}
 		}
 
 
 		@Bean
 		@Bean
 		OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
 		OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
-			DefaultPasswordTokenResponseClient tokenResponseClient = new DefaultPasswordTokenResponseClient();
-			tokenResponseClient.setRestOperations(restOperations());
-			return spy(tokenResponseClient);
+			return new MockPasswordClient();
 		}
 		}
 
 
-		// NOTE: This is autoconfigured in OAuth2LoginConfigurer and
-		// OAuth2ClientConfigurer
 		@Bean
 		@Bean
-		OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
-			DefaultOAuth2UserService userService = new DefaultOAuth2UserService();
-			userService.setRestOperations(restOperations());
-			return spy(userService);
+		OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient() {
+			return new MockJwtBearerClient();
 		}
 		}
 
 
-		// NOTE: This is autoconfigured in OAuth2LoginConfigurer and
-		// OAuth2ClientConfigurer
 		@Bean
 		@Bean
-		OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
-			OidcUserService userService = new OidcUserService();
-			userService.setOauth2UserService(oauth2UserService());
-			return spy(userService);
+		OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
+			return mock(DefaultOAuth2UserService.class);
 		}
 		}
 
 
 		@Bean
 		@Bean
-		RestOperations restOperations() {
-			// Minimum required configuration
-			RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(),
-					new OAuth2AccessTokenResponseHttpMessageConverter(), new MappingJackson2HttpMessageConverter()));
-			restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
-
-			// TODO Add custom configuration, eg. Proxy, TLS, etc
-
-			return spy(restTemplate);
+		OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
+			return mock(OidcUserService.class);
 		}
 		}
 
 
 	}
 	}
@@ -169,22 +394,35 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
 
 
 		@Bean
 		@Bean
 		AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeProvider() {
 		AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeProvider() {
-			return mock(AuthorizationCodeOAuth2AuthorizedClientProvider.class);
+			return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
 		}
 		}
 
 
 		@Bean
 		@Bean
 		RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() {
 		RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() {
-			return mock(RefreshTokenOAuth2AuthorizedClientProvider.class);
+			RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
+			authorizedClientProvider.setAccessTokenResponseClient(new MockRefreshTokenClient());
+			return authorizedClientProvider;
 		}
 		}
 
 
 		@Bean
 		@Bean
 		ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() {
 		ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() {
-			return mock(ClientCredentialsOAuth2AuthorizedClientProvider.class);
+			ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
+			authorizedClientProvider.setAccessTokenResponseClient(new MockClientCredentialsClient());
+			return authorizedClientProvider;
 		}
 		}
 
 
 		@Bean
 		@Bean
 		PasswordOAuth2AuthorizedClientProvider passwordProvider() {
 		PasswordOAuth2AuthorizedClientProvider passwordProvider() {
-			return mock(PasswordOAuth2AuthorizedClientProvider.class);
+			PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
+			authorizedClientProvider.setAccessTokenResponseClient(new MockPasswordClient());
+			return authorizedClientProvider;
+		}
+
+		@Bean
+		JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
+			JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
+			authorizedClientProvider.setAccessTokenResponseClient(new MockJwtBearerClient());
+			return authorizedClientProvider;
 		}
 		}
 
 
 	}
 	}
@@ -195,8 +433,7 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
 		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
 			// @formatter:off
 			// @formatter:off
 			http
 			http
-				.authorizeHttpRequests(authorize ->
-					authorize.anyRequest().authenticated())
+				.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
 				.oauth2Login(Customizer.withDefaults())
 				.oauth2Login(Customizer.withDefaults())
 				.oauth2Client(Customizer.withDefaults());
 				.oauth2Client(Customizer.withDefaults());
 			return http.build();
 			return http.build();
@@ -205,7 +442,29 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
 
 
 		@Bean
 		@Bean
 		ClientRegistrationRepository clientRegistrationRepository() {
 		ClientRegistrationRepository clientRegistrationRepository() {
-			return mock(ClientRegistrationRepository.class);
+			// @formatter:off
+			return new InMemoryClientRegistrationRepository(Arrays.asList(
+					CommonOAuth2Provider.GOOGLE.getBuilder("google")
+							.clientId("google-client-id")
+							.clientSecret("google-client-secret")
+							.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+							.build(),
+					CommonOAuth2Provider.GITHUB.getBuilder("github")
+							.clientId("github-client-id")
+							.clientSecret("github-client-secret")
+							.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+							.build(),
+					CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
+							.clientId("facebook-client-id")
+							.clientSecret("facebook-client-secret")
+							.authorizationGrantType(AuthorizationGrantType.PASSWORD)
+							.build(),
+					CommonOAuth2Provider.OKTA.getBuilder("okta")
+							.clientId("okta-client-id")
+							.clientSecret("okta-client-secret")
+							.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
+							.build()));
+			// @formatter:on
 		}
 		}
 
 
 		@Bean
 		@Bean
@@ -213,6 +472,76 @@ public class OAuth2AuthorizedClientManagerConfigurationTests {
 			return mock(OAuth2AuthorizedClientRepository.class);
 			return mock(OAuth2AuthorizedClientRepository.class);
 		}
 		}
 
 
+		@Bean
+		Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
+			return (authorizedClientManager) -> authorizedClientManager
+					.setContextAttributesMapper((authorizeRequest) -> {
+						HttpServletRequest request = Objects
+								.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
+						String username = request.getParameter(OAuth2ParameterNames.USERNAME);
+						String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
+
+						Map<String, Object> attributes = Collections.emptyMap();
+						if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
+							attributes = new HashMap<>();
+							attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
+							attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
+						}
+
+						return attributes;
+					});
+		}
+
+	}
+
+	private static class MockAuthorizationCodeClient
+			implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(
+				OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockRefreshTokenClient
+			implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockClientCredentialsClient
+			implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(
+				OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
 	}
 	}
 
 
 }
 }

+ 3 - 2
config/src/test/java/org/springframework/security/config/annotation/web/configuration/OAuth2ClientConfigurationTests.java

@@ -175,9 +175,10 @@ public class OAuth2ClientConfigurationTests {
 	@Test
 	@Test
 	public void loadContextWhenAccessTokenResponseClientRegisteredTwiceThenThrowNoUniqueBeanDefinitionException() {
 	public void loadContextWhenAccessTokenResponseClientRegisteredTwiceThenThrowNoUniqueBeanDefinitionException() {
 		// @formatter:off
 		// @formatter:off
-		assertThatExceptionOfType(Exception.class)
+		assertThatExceptionOfType(BeanCreationException.class)
 				.isThrownBy(() -> this.spring.register(AccessTokenResponseClientRegisteredTwiceConfig.class).autowire())
 				.isThrownBy(() -> this.spring.register(AccessTokenResponseClientRegisteredTwiceConfig.class).autowire())
-				.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class)
+				.havingRootCause()
+				.isInstanceOf(NoUniqueBeanDefinitionException.class)
 				.withMessageContaining(
 				.withMessageContaining(
 						"expected single matching bean but found 2: accessTokenResponseClient1,accessTokenResponseClient2");
 						"expected single matching bean but found 2: accessTokenResponseClient1,accessTokenResponseClient2");
 		// @formatter:on
 		// @formatter:on

+ 475 - 0
config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java

@@ -0,0 +1,475 @@
+/*
+ * Copyright 2002-2023 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.config.http;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
+import org.springframework.security.config.test.SpringTestContext;
+import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
+import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
+import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
+import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
+import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
+import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
+import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
+import org.springframework.security.oauth2.jwt.JoseHeaderNames;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.jwt.JwtClaimNames;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link OAuth2AuthorizedClientManagerRegistrar}.
+ *
+ * @author Steve Riesenberg
+ */
+public class OAuth2AuthorizedClientManagerRegistrarTests {
+
+	private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests";
+
+	private static OAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	private OAuth2AuthorizedClientManager authorizedClientManager;
+
+	@Autowired
+	private ClientRegistrationRepository clientRegistrationRepository;
+
+	@Autowired
+	private OAuth2AuthorizedClientRepository authorizedClientRepository;
+
+	@Autowired(required = false)
+	private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	@BeforeEach
+	@SuppressWarnings("unchecked")
+	public void setUp() {
+		MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class);
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+	}
+
+	@Test
+	public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
+		this.spring.configLocations(xml("minimal")).autowire();
+		assertThat(this.authorizedClientManager).isNotNull();
+	}
+
+	@Test
+	public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
+		this.spring.configLocations(xml("providers")).autowire();
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId("google")
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
+				.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest))
+				.extracting(OAuth2AuthorizationException::getError)
+				.extracting(OAuth2Error::getErrorCode)
+				.isEqualTo("client_authorization_required");
+		// @formatter:on
+
+		verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
+	}
+
+	@Test
+	public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
+		this.spring.configLocations(xml("clients")).autowire();
+		testRefreshTokenGrant();
+	}
+
+	@Test
+	public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
+		this.spring.configLocations(xml("providers")).autowire();
+		testRefreshTokenGrant();
+	}
+
+	private void testRefreshTokenGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
+		OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
+				authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
+		this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request,
+				this.response);
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withAuthorizedClient(existingAuthorizedClient)
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2RefreshTokenGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
+		assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
+		assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
+	}
+
+	@Test
+	public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
+		this.spring.configLocations(xml("clients")).autowire();
+		testClientCredentialsGrant();
+	}
+
+	@Test
+	public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
+		this.spring.configLocations(xml("providers")).autowire();
+		testClientCredentialsGrant();
+	}
+
+	private void testClientCredentialsGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2ClientCredentialsGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
+	}
+
+	@Test
+	public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
+		this.spring.configLocations(xml("clients")).autowire();
+		testPasswordGrant();
+	}
+
+	@Test
+	public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
+		this.spring.configLocations(xml("providers")).autowire();
+		testPasswordGrant();
+	}
+
+	private void testPasswordGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
+				.willReturn(accessTokenResponse);
+
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
+		this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
+				.forClass(OAuth2PasswordGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
+		assertThat(grantRequest.getUsername()).isEqualTo("user");
+		assertThat(grantRequest.getPassword()).isEqualTo("password");
+	}
+
+	@Test
+	public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
+		this.spring.configLocations(xml("clients")).autowire();
+		testJwtBearerGrant();
+	}
+
+	@Test
+	public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
+		this.spring.configLocations(xml("providers")).autowire();
+		testJwtBearerGrant();
+	}
+
+	private void testJwtBearerGrant() {
+		OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
+		given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse);
+
+		JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
+		ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
+		// @formatter:off
+		OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
+				.withClientRegistrationId(clientRegistration.getRegistrationId())
+				.principal(authentication)
+				.attribute(HttpServletRequest.class.getName(), this.request)
+				.attribute(HttpServletResponse.class.getName(), this.response)
+				.build();
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
+		assertThat(authorizedClient).isNotNull();
+
+		ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
+		verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
+
+		JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
+		assertThat(grantRequest.getClientRegistration().getRegistrationId())
+				.isEqualTo(clientRegistration.getRegistrationId());
+		assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
+		assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
+	}
+
+	private static OAuth2AccessToken getExpiredAccessToken() {
+		Instant expiresAt = Instant.now().minusSeconds(60);
+		Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
+		return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
+				new HashSet<>(Arrays.asList("read", "write")));
+	}
+
+	private static Jwt getJwt() {
+		Instant issuedAt = Instant.now();
+		return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
+				Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
+				Collections.singletonMap(JwtClaimNames.SUB, "user"));
+	}
+
+	private static String xml(String configName) {
+		return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
+	}
+
+	public static List<ClientRegistration> getClientRegistrations() {
+		// @formatter:off
+		return Arrays.asList(
+				CommonOAuth2Provider.GOOGLE.getBuilder("google")
+						.clientId("google-client-id")
+						.clientSecret("google-client-secret")
+						.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+						.build(),
+				CommonOAuth2Provider.GITHUB.getBuilder("github")
+						.clientId("github-client-id")
+						.clientSecret("github-client-secret")
+						.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+						.build(),
+				CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
+						.clientId("facebook-client-id")
+						.clientSecret("facebook-client-secret")
+						.authorizationGrantType(AuthorizationGrantType.PASSWORD)
+						.build(),
+				CommonOAuth2Provider.OKTA.getBuilder("okta")
+						.clientId("okta-client-id")
+						.clientSecret("okta-client-secret")
+						.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
+						.build());
+		// @formatter:on
+	}
+
+	public static Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
+		return (authorizedClientManager) -> authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
+			HttpServletRequest request = Objects
+					.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
+			String username = request.getParameter(OAuth2ParameterNames.USERNAME);
+			String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
+
+			Map<String, Object> attributes = Collections.emptyMap();
+			if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
+				attributes = new HashMap<>();
+				attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
+				attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
+			}
+
+			return attributes;
+		});
+	}
+
+	public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider() {
+		return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
+	}
+
+	public static RefreshTokenOAuth2AuthorizedClientProvider refreshTokenAuthorizedClientProvider() {
+		RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
+		authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient());
+		return authorizedClientProvider;
+	}
+
+	public static MockRefreshTokenClient refreshTokenAccessTokenResponseClient() {
+		return new MockRefreshTokenClient();
+	}
+
+	public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider() {
+		ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
+		authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient());
+		return authorizedClientProvider;
+	}
+
+	public static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
+		return new MockClientCredentialsClient();
+	}
+
+	public static PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider() {
+		PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
+		authorizedClientProvider.setAccessTokenResponseClient(passwordAccessTokenResponseClient());
+		return authorizedClientProvider;
+	}
+
+	public static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
+		return new MockPasswordClient();
+	}
+
+	public static JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
+		JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
+		authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient());
+		return authorizedClientProvider;
+	}
+
+	public static OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
+		return new MockJwtBearerClient();
+	}
+
+	private static class MockAuthorizationCodeClient
+			implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(
+				OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockRefreshTokenClient
+			implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockClientCredentialsClient
+			implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(
+				OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+	private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
+
+		@Override
+		public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
+			return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
+		}
+
+	}
+
+}

+ 56 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-clients.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2023 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client/>
+		<intercept-url pattern="/**" access="authenticated"/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+
+	<b:bean class="org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+					factory-method="getClientRegistrations"/>
+		</b:constructor-arg>
+	</b:bean>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="authorizedClientManagerConsumer"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="refreshTokenAccessTokenResponseClient"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="clientCredentialsAccessTokenResponseClient"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="passwordAccessTokenResponseClient"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="jwtBearerAccessTokenResponseClient"/>
+
+</b:beans>

+ 41 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-minimal.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2023 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client/>
+		<intercept-url pattern="/**" access="authenticated"/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+
+	<b:bean class="org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+					factory-method="getClientRegistrations"/>
+		</b:constructor-arg>
+	</b:bean>
+
+</b:beans>

+ 59 - 0
config/src/test/resources/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests-providers.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2023 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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<oauth2-client/>
+		<intercept-url pattern="/**" access="authenticated"/>
+	</http>
+
+	<b:import resource="userservice.xml"/>
+
+	<b:bean class="org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository">
+		<b:constructor-arg>
+			<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+					factory-method="getClientRegistrations"/>
+		</b:constructor-arg>
+	</b:bean>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="authorizedClientManagerConsumer"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="authorizationCodeAuthorizedClientProvider"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="refreshTokenAuthorizedClientProvider"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="clientCredentialsAuthorizedClientProvider"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="passwordAuthorizedClientProvider"/>
+
+	<b:bean class="org.springframework.security.config.http.OAuth2AuthorizedClientManagerRegistrarTests"
+			factory-method="jwtBearerAuthorizedClientProvider"/>
+
+</b:beans>