From e61c03f7c305b44d912c67b232a70b3a923aeed1 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Wed, 4 Feb 2026 15:40:55 -0500 Subject: [PATCH 1/2] Fix to allow multiple PasswordEncoder beans Closes gh-18645 --- .../authorization/OAuth2ConfigurerUtils.java | 20 +--------- .../OAuth2ClientCredentialsGrantTests.java | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java index c87ebe2c76..f2082c686c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java @@ -16,12 +16,9 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; -import java.util.Map; - import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.context.ApplicationContext; @@ -45,7 +42,6 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; /** * Utility methods for the OAuth 2.0 Configurers. @@ -224,24 +220,12 @@ final class OAuth2ConfigurerUtils { } static T getOptionalBean(HttpSecurity httpSecurity, Class type) { - Map beansMap = BeanFactoryUtils - .beansOfTypeIncludingAncestors(httpSecurity.getSharedObject(ApplicationContext.class), type); - if (beansMap.size() > 1) { - throw new NoUniqueBeanDefinitionException(type, beansMap.size(), - "Expected single matching bean of type '" + type.getName() + "' but found " + beansMap.size() + ": " - + StringUtils.collectionToCommaDelimitedString(beansMap.keySet())); - } - return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null); + return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); } @SuppressWarnings("unchecked") static T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { - ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); - String[] names = context.getBeanNamesForType(type); - if (names.length > 1) { - throw new NoUniqueBeanDefinitionException(type, names); - } - return (names.length == 1) ? (T) context.getBean(names[0]) : null; + return (T) httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java index 6e0dc3ae83..8b27bad816 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java @@ -45,6 +45,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; @@ -158,6 +159,8 @@ public class OAuth2ClientCredentialsGrantTests { private static AuthenticationFailureHandler authenticationFailureHandler; + private static PasswordEncoder passwordEncoder; + public final SpringTestContext spring = new SpringTestContext(this); @Autowired @@ -183,6 +186,9 @@ public class OAuth2ClientCredentialsGrantTests { authenticationProvidersConsumer = mock(Consumer.class); authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); authenticationFailureHandler = mock(AuthenticationFailureHandler.class); + passwordEncoder = mock(PasswordEncoder.class); + given(passwordEncoder.matches(any(), any())).willReturn(true); + given(passwordEncoder.upgradeEncoding(any())).willReturn(false); db = new EmbeddedDatabaseBuilder().generateUniqueName(true) .setType(EmbeddedDatabaseType.HSQL) .setScriptEncoding("UTF-8") @@ -496,6 +502,26 @@ public class OAuth2ClientCredentialsGrantTests { .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); } + @Test + public void requestWhenTokenRequestWithMultiplePasswordEncodersThenPrimaryPasswordEncoderUsed() throws Exception { + this.spring.register(AuthorizationServerConfigurationWithMultiplePasswordEncoders.class).autowire(); + + RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); + this.registeredClientRepository.save(registeredClient); + + this.mvc + .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) + .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") + .header(HttpHeaders.AUTHORIZATION, + "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.access_token").isNotEmpty()) + .andExpect(jsonPath("$.scope").value("scope1 scope2")); + + verify(passwordEncoder).matches(any(), any()); + } + private static String generateDPoPProof(String tokenEndpointUri) { // @formatter:off Map publicJwk = TestJwks.DEFAULT_EC_JWK @@ -658,4 +684,16 @@ public class OAuth2ClientCredentialsGrantTests { } + @EnableWebSecurity + @Configuration(proxyBeanMethods = false) + static class AuthorizationServerConfigurationWithMultiplePasswordEncoders extends AuthorizationServerConfiguration { + + @Primary + @Bean + PasswordEncoder primaryPasswordEncoder() { + return passwordEncoder; + } + + } + } From d3c42a7a4f15f35ac81d05b4e7641151df24aea7 Mon Sep 17 00:00:00 2001 From: Joe Grandja <10884212+jgrandja@users.noreply.github.com> Date: Wed, 4 Feb 2026 16:01:53 -0500 Subject: [PATCH 2/2] Polish OAuth2ConfigurerUtils --- .../authorization/OAuth2ConfigurerUtils.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java index f2082c686c..69152224c6 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ConfigurerUtils.java @@ -19,8 +19,6 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.context.ApplicationContext; import org.springframework.core.ResolvableType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -203,20 +201,7 @@ final class OAuth2ConfigurerUtils { } static T getBean(HttpSecurity httpSecurity, Class type) { - return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type); - } - - @SuppressWarnings("unchecked") - static T getBean(HttpSecurity httpSecurity, ResolvableType type) { - ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class); - String[] names = context.getBeanNamesForType(type); - if (names.length == 1) { - return (T) context.getBean(names[0]); - } - if (names.length > 1) { - throw new NoUniqueBeanDefinitionException(type, names); - } - throw new NoSuchBeanDefinitionException(type); + return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getObject(); } static T getOptionalBean(HttpSecurity httpSecurity, Class type) {