PasswordEncoder Bean for AuthenticationManagerBuilder

Issue: gh-4873
This commit is contained in:
Rob Winch 2017-11-27 10:57:28 -06:00
parent 9afee9e4e2
commit 691bf2e11d
6 changed files with 248 additions and 18 deletions

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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;
}

View File

@ -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");