From f34b459c80e8d083cbe53586ccceb80c568011e1 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Mon, 22 Jul 2013 16:38:09 -0500 Subject: [PATCH] SEC-2205: Create UserDetailsServiceDelegator Ensure that the UserDetailsService is created lazily. --- .../WebSecurityConfigurerAdapter.java | 39 ++++++++- .../WebSecurityConfigurerAdapterTests.groovy | 87 ++++++++++++++++--- 2 files changed, 112 insertions(+), 14 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java index ea0f08951b..61ba36ac07 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.java @@ -35,7 +35,9 @@ import org.springframework.security.config.annotation.web.configurers.DefaultLog import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer; import org.springframework.security.core.Authentication; 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.web.access.intercept.FilterSecurityInterceptor; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy; @@ -238,7 +240,7 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer * @see {@link #userDetailsService()} */ public UserDetailsService userDetailsServiceBean() throws Exception { - return userDetailsService(); + return new UserDetailsServiceDelegator(parentAuthenticationBuilder); } /** @@ -320,6 +322,41 @@ public abstract class WebSecurityConfigurerAdapter implements SecurityConfigurer } + /** + * Delays the use of the {@link UserDetailsService} from the + * {@link AuthenticationManagerBuilder} to ensure that it has been fully + * configured. + * + * @author Rob Winch + * @since 3.2 + */ + static final class UserDetailsServiceDelegator implements UserDetailsService { + private AuthenticationManagerBuilder delegateBuilder; + private UserDetailsService delegate; + private final Object delegateMonitor = new Object(); + + UserDetailsServiceDelegator(AuthenticationManagerBuilder authentication) { + this.delegateBuilder = authentication; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + if(delegate != null) { + return delegate.loadUserByUsername(username); + } + + synchronized(delegateMonitor) { + if (delegate == null) { + delegate = this.delegateBuilder.getDefaultUserDetailsService(); + this.delegateBuilder = null; + } + } + + return delegate.loadUserByUsername(username); + } + } + + /** * Delays the use of the {@link AuthenticationManager} build from the * {@link AuthenticationManagerBuilder} to ensure that it has been fully diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy index ff0372bac9..85ee6e215e 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/WebSecurityConfigurerAdapterTests.groovy @@ -15,33 +15,35 @@ */ package org.springframework.security.config.annotation.web; -import static org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTestsConfigs.* import static org.junit.Assert.* +import static org.springframework.security.config.annotation.web.WebSecurityConfigurerAdapterTestsConfigs.* -import javax.sql.DataSource +import javax.servlet.FilterChain +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse +import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationListener import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType -import org.springframework.ldap.core.support.BaseLdapPathContextSource import org.springframework.security.authentication.AuthenticationManager import org.springframework.security.authentication.AuthenticationProvider import org.springframework.security.authentication.DefaultAuthenticationEventPublisher import org.springframework.security.authentication.UsernamePasswordAuthenticationToken import org.springframework.security.authentication.event.AuthenticationSuccessEvent import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.authentication.configurers.ldap.LdapAuthenticationProviderConfigurer; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter import org.springframework.security.core.Authentication -import org.springframework.security.core.authority.AuthorityUtils -import org.springframework.security.ldap.DefaultSpringSecurityContextSource -import org.springframework.web.accept.ContentNegotiationStrategy; -import org.springframework.web.accept.HeaderContentNegotiationStrategy; +import org.springframework.security.core.userdetails.UserDetailsService +import org.springframework.security.core.userdetails.UsernameNotFoundException +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter +import org.springframework.web.accept.ContentNegotiationStrategy +import org.springframework.web.accept.HeaderContentNegotiationStrategy +import org.springframework.web.filter.OncePerRequestFilter /** * @author Rob Winch @@ -132,4 +134,63 @@ class WebSecurityConfigurerAdapterTests extends BaseSpringSpec { @EnableWebSecurity @Configuration static class ContentNegotiationStrategyDefaultSharedObjectConfig extends WebSecurityConfigurerAdapter {} + + def "UserDetailsService lazy"() { + setup: + loadConfig(RequiresUserDetailsServiceConfig,UserDetailsServiceConfig) + when: + findFilter(MyFilter).userDetailsService.loadUserByUsername("user") + then: + noExceptionThrown() + when: + findFilter(MyFilter).userDetailsService.loadUserByUsername("admin") + then: + thrown(UsernameNotFoundException) + } + + @Configuration + static class RequiresUserDetailsServiceConfig { + @Bean + public MyFilter myFilter(UserDetailsService uds) { + return new MyFilter(uds) + } + } + + @Configuration + @EnableWebSecurity + static class UserDetailsServiceConfig extends WebSecurityConfigurerAdapter { + @Autowired + private MyFilter myFilter; + + @Bean + @Override + public UserDetailsService userDetailsServiceBean() { + return super.userDetailsServiceBean() + } + + @Override + public void configure(HttpSecurity http) { + http + .addFilterBefore(myFilter,UsernamePasswordAuthenticationFilter) + } + + @Override + protected void registerAuthentication(AuthenticationManagerBuilder auth) + throws Exception { + auth + .inMemoryAuthentication() + .withUser("user").password("password").roles("USER") + } + } + + static class MyFilter extends OncePerRequestFilter { + private UserDetailsService userDetailsService + public MyFilter(UserDetailsService uds) { + assert uds != null + this.userDetailsService = uds + } + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { + chain.doFilter(request,response) + } + } }