浏览代码

PasswordEncoder Bean for AuthenticationManagerBuilder

Issue: gh-4873
Rob Winch 7 年之前
父节点
当前提交
691bf2e11d

+ 93 - 4
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/AuthenticationConfiguration.java

@@ -27,6 +27,7 @@ import org.apache.commons.logging.LogFactory;
 import org.springframework.aop.framework.ProxyFactoryBean;
 import org.springframework.aop.target.LazyInitTargetSource;
 import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
@@ -36,9 +37,15 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
 import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.util.Assert;
 
 /**
@@ -67,8 +74,9 @@ public class AuthenticationConfiguration {
 
 	@Bean
 	public AuthenticationManagerBuilder authenticationManagerBuilder(
-			ObjectPostProcessor<Object> objectPostProcessor) {
-		return new AuthenticationManagerBuilder(objectPostProcessor);
+			ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
+		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
+		return new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
 	}
 
 	@Bean
@@ -92,7 +100,7 @@ public class AuthenticationConfiguration {
 			return this.authenticationManager;
 		}
 		AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
-				this.objectPostProcessor);
+				this.objectPostProcessor, this.applicationContext);
 		if (this.buildingAuthenticationManager.getAndSet(true)) {
 			return new AuthenticationManagerDelegator(authBuilder);
 		}
@@ -210,4 +218,85 @@ public class AuthenticationConfiguration {
 			return "AuthenticationManagerDelegator [delegate=" + this.delegate + "]";
 		}
 	}
-}
+
+	static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder {
+		private PasswordEncoder defaultPasswordEncoder;
+
+		/**
+		 * Creates a new instance
+		 *
+		 * @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
+		 */
+		DefaultPasswordEncoderAuthenticationManagerBuilder(
+			ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) {
+			super(objectPostProcessor);
+			this.defaultPasswordEncoder = defaultPasswordEncoder;
+		}
+
+		@Override
+		public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
+			throws Exception {
+			return super.inMemoryAuthentication()
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+
+		@Override
+		public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
+			throws Exception {
+			return super.jdbcAuthentication()
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+
+		@Override
+		public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
+			T userDetailsService) throws Exception {
+			return super.userDetailsService(userDetailsService)
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+	}
+
+	static class LazyPasswordEncoder implements PasswordEncoder {
+		private ApplicationContext applicationContext;
+		private PasswordEncoder passwordEncoder;
+
+		LazyPasswordEncoder(ApplicationContext applicationContext) {
+			this.applicationContext = applicationContext;
+		}
+
+		@Override
+		public String encode(CharSequence rawPassword) {
+			return getPasswordEncoder().encode(rawPassword);
+		}
+
+		@Override
+		public boolean matches(CharSequence rawPassword,
+			String encodedPassword) {
+			return getPasswordEncoder().matches(rawPassword, encodedPassword);
+		}
+
+		private PasswordEncoder getPasswordEncoder() {
+			if (this.passwordEncoder != null) {
+				return this.passwordEncoder;
+			}
+			PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
+			if (passwordEncoder == null) {
+				passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+			}
+			this.passwordEncoder = passwordEncoder;
+			return passwordEncoder;
+		}
+
+		private <T> T getBeanOrNull(Class<T> type) {
+			try {
+				return this.applicationContext.getBean(type);
+			} catch(NoSuchBeanDefinitionException notFound) {
+				return null;
+			}
+		}
+
+		@Override
+		public String toString() {
+			return getPasswordEncoder().toString();
+		}
+	}
+}

+ 1 - 0
config/src/main/java/org/springframework/security/config/annotation/authentication/configuration/InitializeUserDetailsBeanManagerConfigurer.java

@@ -71,6 +71,7 @@ class InitializeUserDetailsBeanManagerConfigurer
 			if (passwordEncoder != null) {
 				provider.setPasswordEncoder(passwordEncoder);
 			}
+			provider.afterPropertiesSet();
 
 			auth.authenticationProvider(provider);
 		}

+ 99 - 11
config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java

@@ -31,6 +31,7 @@ import org.springframework.aop.framework.Advised;
 import org.springframework.aop.target.LazyInitTargetSource;
 import org.springframework.beans.FatalBeanException;
 import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.core.annotation.Order;
@@ -42,6 +43,9 @@ import org.springframework.security.authentication.DefaultAuthenticationEventPub
 import org.springframework.security.config.annotation.ObjectPostProcessor;
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
+import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
 import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -53,6 +57,8 @@ import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
 import org.springframework.util.Assert;
@@ -365,6 +371,19 @@ public abstract class WebSecurityConfigurerAdapter implements
 	@Autowired
 	public void setApplicationContext(ApplicationContext context) {
 		this.context = context;
+
+		ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
+		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
+
+		authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
+		localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
+			@Override
+			public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
+				authenticationBuilder.eraseCredentials(eraseCredentials);
+				return super.eraseCredentials(eraseCredentials);
+			}
+
+		};
 	}
 
 	@Autowired(required = false)
@@ -381,17 +400,6 @@ public abstract class WebSecurityConfigurerAdapter implements
 	@Autowired
 	public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
 		this.objectPostProcessor = objectPostProcessor;
-
-		authenticationBuilder = new AuthenticationManagerBuilder(objectPostProcessor);
-		localConfigureAuthenticationBldr = new AuthenticationManagerBuilder(
-				objectPostProcessor) {
-			@Override
-			public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
-				authenticationBuilder.eraseCredentials(eraseCredentials);
-				return super.eraseCredentials(eraseCredentials);
-			}
-
-		};
 	}
 
 	@Autowired
@@ -530,4 +538,84 @@ public abstract class WebSecurityConfigurerAdapter implements
 		}
 	}
 
+	static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder {
+		private PasswordEncoder defaultPasswordEncoder;
+
+		/**
+		 * Creates a new instance
+		 *
+		 * @param objectPostProcessor the {@link ObjectPostProcessor} instance to use.
+		 */
+		DefaultPasswordEncoderAuthenticationManagerBuilder(
+			ObjectPostProcessor<Object> objectPostProcessor, PasswordEncoder defaultPasswordEncoder) {
+			super(objectPostProcessor);
+			this.defaultPasswordEncoder = defaultPasswordEncoder;
+		}
+
+		@Override
+		public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
+			throws Exception {
+			return super.inMemoryAuthentication()
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+
+		@Override
+		public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
+			throws Exception {
+			return super.jdbcAuthentication()
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+
+		@Override
+		public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
+			T userDetailsService) throws Exception {
+			return super.userDetailsService(userDetailsService)
+				.passwordEncoder(this.defaultPasswordEncoder);
+		}
+	}
+
+	static class LazyPasswordEncoder implements PasswordEncoder {
+		private ApplicationContext applicationContext;
+		private PasswordEncoder passwordEncoder;
+
+		LazyPasswordEncoder(ApplicationContext applicationContext) {
+			this.applicationContext = applicationContext;
+		}
+
+		@Override
+		public String encode(CharSequence rawPassword) {
+			return getPasswordEncoder().encode(rawPassword);
+		}
+
+		@Override
+		public boolean matches(CharSequence rawPassword,
+			String encodedPassword) {
+			return getPasswordEncoder().matches(rawPassword, encodedPassword);
+		}
+
+		private PasswordEncoder getPasswordEncoder() {
+			if (this.passwordEncoder != null) {
+				return this.passwordEncoder;
+			}
+			PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
+			if (passwordEncoder == null) {
+				passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
+			}
+			this.passwordEncoder = passwordEncoder;
+			return passwordEncoder;
+		}
+
+		private <T> T getBeanOrNull(Class<T> type) {
+			try {
+				return this.applicationContext.getBean(type);
+			} catch(NoSuchBeanDefinitionException notFound) {
+				return null;
+			}
+		}
+
+		@Override
+		public String toString() {
+			return getPasswordEncoder().toString();
+		}
+	}
 }

+ 52 - 0
config/src/test/groovy/org/springframework/security/config/annotation/authentication/AuthenticationManagerBuilderTests.groovy

@@ -15,6 +15,7 @@
  */
 package org.springframework.security.config.annotation.authentication
 
+import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration
@@ -37,6 +38,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
 import org.springframework.security.core.Authentication
 import org.springframework.security.core.userdetails.PasswordEncodedUser
 import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.crypto.password.NoOpPasswordEncoder
+import org.springframework.security.crypto.password.PasswordEncoder
 import org.springframework.security.provisioning.InMemoryUserDetailsManager;
 
 /**
@@ -71,6 +74,55 @@ class AuthenticationManagerBuilderTests extends BaseSpringSpec {
 			am.eventPublisher == aep
 	}
 
+	def "PasswordEncoder bean is used for Global"() {
+		setup:
+		loadConfig(PasswordEncoderGlobalConfig)
+		when:
+		Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+		then:
+		auth.name == "user"
+		auth.authorities*.authority == ['ROLE_USER']
+	}
+
+	@EnableWebSecurity
+	static class PasswordEncoderGlobalConfig extends WebSecurityConfigurerAdapter {
+		@Autowired
+		void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+			auth
+				.inMemoryAuthentication()
+				.withUser("user").password("password").roles("USER")
+		}
+
+		@Bean
+		PasswordEncoder passwordEncoder() {
+			return NoOpPasswordEncoder.getInstance();
+		}
+	}
+
+	def "PasswordEncoder bean is used for protected"() {
+		setup:
+		loadConfig(PasswordEncoderConfig)
+		when:
+		Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("user","password"))
+		then:
+		auth.name == "user"
+		auth.authorities*.authority == ['ROLE_USER']
+	}
+
+	@EnableWebSecurity
+	static class PasswordEncoderConfig extends WebSecurityConfigurerAdapter {
+		void configure(AuthenticationManagerBuilder auth) throws Exception {
+			auth
+				.inMemoryAuthentication()
+				.withUser("user").password("password").roles("USER")
+		}
+
+		@Bean
+		PasswordEncoder passwordEncoder() {
+			return NoOpPasswordEncoder.getInstance();
+		}
+	}
+
 	def "authentication-manager support multiple DaoAuthenticationProvider's"() {
 		setup:
 			loadConfig(MultiAuthenticationProvidersConfig)

+ 1 - 2
core/src/main/java/org/springframework/security/authentication/dao/DaoAuthenticationProvider.java

@@ -94,6 +94,7 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
 
 	protected void doAfterPropertiesSet() throws Exception {
 		Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
+		this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
 	}
 
 	protected final UserDetails retrieveUser(String username,
@@ -138,8 +139,6 @@ public class DaoAuthenticationProvider extends AbstractUserDetailsAuthentication
 	 */
 	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
 		Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
-
-		this.userNotFoundEncodedPassword = passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
 		this.passwordEncoder = passwordEncoder;
 	}
 

+ 2 - 1
core/src/test/java/org/springframework/security/authentication/dao/DaoAuthenticationProviderTests.java

@@ -483,7 +483,7 @@ public class DaoAuthenticationProviderTests {
 
 	// SEC-2056
 	@Test
-	public void testUserNotFoundEncodesPassword() {
+	public void testUserNotFoundEncodesPassword() throws Exception {
 		UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
 				"missing", "koala");
 		PasswordEncoder encoder = mock(PasswordEncoder.class);
@@ -492,6 +492,7 @@ public class DaoAuthenticationProviderTests {
 		provider.setHideUserNotFoundExceptions(false);
 		provider.setPasswordEncoder(encoder);
 		provider.setUserDetailsService(new MockAuthenticationDaoUserrod());
+		provider.afterPropertiesSet();
 		try {
 			provider.authenticate(token);
 			fail("Expected Exception");