diff --git a/core/src/main/java/org/springframework/security/config/BeanIds.java b/core/src/main/java/org/springframework/security/config/BeanIds.java index 651300627f..c0093163d3 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -17,6 +17,7 @@ public abstract class BeanIds { static final String INTERCEPT_METHODS_BEAN_FACTORY_POST_PROCESSOR = "_interceptMethodsBeanfactoryPP"; static final String CONTEXT_SOURCE_SETTING_POST_PROCESSOR = "_contextSettingPostProcessor"; static final String HTTP_POST_PROCESSOR = "_httpConfigBeanFactoryPostProcessor"; + static final String FILTER_CHAIN_POST_PROCESSOR = "_filterChainProxyPostProcessor"; public static final String JDBC_USER_DETAILS_MANAGER = "_jdbcUserDetailsManager"; public static final String USER_DETAILS_SERVICE = "_userDetailsService"; @@ -59,4 +60,5 @@ public abstract class BeanIds { public static final String X509_FILTER = "_x509ProcessingFilter"; public static final String X509_AUTH_PROVIDER = "_x509AuthenitcationProvider"; public static final String PRE_AUTH_ENTRY_POINT = "_preAuthenticatedProcessingFilterEntryPoint"; + public static final String REMEMBER_ME_SERVICES_INJECTION_POST_PROCESSOR = "_rememberMeServicesInjectionBeanPostProcessor"; } diff --git a/core/src/main/java/org/springframework/security/config/ConfigUtils.java b/core/src/main/java/org/springframework/security/config/ConfigUtils.java index 8d5ed9d1f0..3eb82b393c 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -2,6 +2,7 @@ package org.springframework.security.config; import org.springframework.beans.factory.config.BeanDefinition; 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.ManagedList; import org.springframework.beans.factory.support.RootBeanDefinition; @@ -78,22 +79,22 @@ public abstract class ConfigUtils { * Obtains a user details service for use in RememberMeServices etc. Will return a caching version * if available so should not be used for beans which need to separate the two. */ - static UserDetailsService getUserDetailsService(ConfigurableListableBeanFactory bf) { - Map services = bf.getBeansOfType(CachingUserDetailsService.class); + static RuntimeBeanReference getUserDetailsService(ConfigurableListableBeanFactory bf) { + String[] services = bf.getBeanNamesForType(CachingUserDetailsService.class, false, false); - if (services.size() == 0) { - services = bf.getBeansOfType(UserDetailsService.class); + if (services.length == 0) { + services = bf.getBeanNamesForType(UserDetailsService.class); } - if (services.size() == 0) { + if (services.length == 0) { throw new IllegalArgumentException("No UserDetailsService registered."); - } else if (services.size() > 1) { + } else if (services.length > 1) { throw new IllegalArgumentException("More than one UserDetailsService registered. Please" + "use a specific Id in your configuration"); } - return (UserDetailsService) services.values().toArray()[0]; + return new RuntimeBeanReference(services[0]); } private static AuthenticationManager getAuthenticationManager(ConfigurableListableBeanFactory bf) { diff --git a/core/src/main/java/org/springframework/security/config/FilterChainProxyPostProcessor.java b/core/src/main/java/org/springframework/security/config/FilterChainProxyPostProcessor.java new file mode 100644 index 0000000000..641a1be157 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/FilterChainProxyPostProcessor.java @@ -0,0 +1,98 @@ +package org.springframework.security.config; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.servlet.Filter; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; +import org.springframework.security.util.FilterChainProxy; +import org.springframework.util.Assert; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 2.0 + */ +public class FilterChainProxyPostProcessor implements BeanPostProcessor, BeanFactoryAware { + private Log logger = LogFactory.getLog(getClass()); + + private ListableBeanFactory beanFactory; + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if(!beanName.equals(BeanIds.FILTER_CHAIN_PROXY)) { + return bean; + } + + FilterChainProxy filterChainProxy = (FilterChainProxy) bean; + // Set the default match + List defaultFilterChain = orderFilters(beanFactory); + + // Note that this returns a copy + Map filterMap = filterChainProxy.getFilterChainMap(); + String allUrlsMatch = filterChainProxy.getMatcher().getUniversalMatchPattern(); + + filterMap.put(allUrlsMatch, defaultFilterChain); + filterChainProxy.setFilterChainMap(filterMap); + + logger.info("Configured filter chain(s): " + filterChainProxy); + + return bean; + } + + private List orderFilters(ListableBeanFactory beanFactory) { + Map filters = beanFactory.getBeansOfType(Filter.class); + + Assert.notEmpty(filters, "No filters found in app context!"); + + Iterator ids = filters.keySet().iterator(); + + List orderedFilters = new ArrayList(); + + while (ids.hasNext()) { + String id = (String) ids.next(); + Filter filter = (Filter) filters.get(id); + + if (filter instanceof FilterChainProxy) { + continue; + } + + // Filters must be Spring security filters or wrapped using + if (!filter.getClass().getName().startsWith("org.springframework.security")) { + continue; + } + + if (!(filter instanceof Ordered)) { + logger.info("Filter " + id + " doesn't implement the Ordered interface, skipping it."); + continue; + } + + orderedFilters.add(filter); + } + + Collections.sort(orderedFilters, new OrderComparator()); + + return orderedFilters; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ListableBeanFactory) beanFactory; + } + +} diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index 7aa087f925..6aa948b032 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -208,6 +208,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { Element sessionControlElt = DomUtils.getChildElementByTagName(element, Elements.CONCURRENT_SESSIONS); if (sessionControlElt != null) { new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext); + logger.info("Concurrent session filter in use, setting 'forceEagerSessionCreation' to true"); + httpScif.getPropertyValues().addPropertyValue("forceEagerSessionCreation", Boolean.TRUE); } String sessionFixationAttribute = element.getAttribute(ATT_SESSION_FIXATION_PROTECTION); @@ -242,6 +244,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { Element rememberMeElt = DomUtils.getChildElementByTagName(element, Elements.REMEMBER_ME); if (rememberMeElt != null || autoConfig) { new RememberMeBeanDefinitionParser().parse(rememberMeElt, parserContext); + // Post processor to inject RememberMeServices into filters which need it + RootBeanDefinition rememberMeInjectionPostProcessor = new RootBeanDefinition(RememberMeServicesInjectionBeanPostProcessor.class); + rememberMeInjectionPostProcessor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(BeanIds.REMEMBER_ME_SERVICES_INJECTION_POST_PROCESSOR, rememberMeInjectionPostProcessor); } Element logoutElt = DomUtils.getChildElementByTagName(element, Elements.LOGOUT); @@ -266,7 +272,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { RootBeanDefinition postProcessor = new RootBeanDefinition(HttpSecurityConfigPostProcessor.class); postProcessor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(BeanIds.HTTP_POST_PROCESSOR, postProcessor); - + + // Post processor specifically to assemble and order the filter chain immediately before the FilterChainProxy is initialized. + RootBeanDefinition filterChainPostProcessor = new RootBeanDefinition(FilterChainProxyPostProcessor.class); + filterChainPostProcessor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + registry.registerBeanDefinition(BeanIds.FILTER_CHAIN_POST_PROCESSOR, filterChainPostProcessor); + return null; } @@ -326,22 +337,22 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { parserContext.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_PROVIDER, openIDProvider); } - if (formLoginFilter == null && openIDFilter == null) { - return; - } + boolean needLoginPage = false; if (formLoginFilter != null) { + needLoginPage = true; parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_FILTER, formLoginFilter); parserContext.getRegistry().registerBeanDefinition(BeanIds.FORM_LOGIN_ENTRY_POINT, formLoginEntryPoint); } if (openIDFilter != null) { + needLoginPage = true; parserContext.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_FILTER, openIDFilter); parserContext.getRegistry().registerBeanDefinition(BeanIds.OPEN_ID_ENTRY_POINT, openIDEntryPoint); } // If no login page has been defined, add in the default page generator. - if (formLoginPage == null && openIDLoginPage == null) { + if (needLoginPage && formLoginPage == null && openIDLoginPage == null) { logger.info("No login page configured. The default internal one will be used. Use the '" + FormLoginBeanDefinitionParser.ATT_LOGIN_PAGE + "' attribute to set the URL of the login page."); BeanDefinitionBuilder loginPageFilter = diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java index 6b6cb817ae..23e1e803fb 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java @@ -1,38 +1,33 @@ package org.springframework.security.config; -import java.util.ArrayList; -import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Map; -import javax.servlet.Filter; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.PropertyValue; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 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.RootBeanDefinition; -import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; -import org.springframework.security.concurrent.ConcurrentSessionFilter; -import org.springframework.security.context.HttpSessionContextIntegrationFilter; import org.springframework.security.ui.AbstractProcessingFilter; import org.springframework.security.ui.AuthenticationEntryPoint; import org.springframework.security.ui.basicauth.BasicProcessingFilter; import org.springframework.security.ui.rememberme.RememberMeServices; import org.springframework.security.userdetails.UserDetailsByNameServiceWrapper; -import org.springframework.security.util.FilterChainProxy; import org.springframework.util.Assert; /** - * Responsible for tying up the HTTP security configuration - building ordered filter stack and linking up - * with other beans. + * Responsible for tying up the HTTP security configuration once all the beans are registered. + * This class does not actually instantiate any beans (for example, it should not call {@link BeanFactory#getBean(String)}). + * All the wiring up should be done using bean definitions or bean references to avoid. This approach should avoid any + * conflict with other processors. * * @author Luke Taylor * @author Ben Alex @@ -46,12 +41,7 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor injectUserDetailsServiceIntoRememberMeServices(beanFactory); injectUserDetailsServiceIntoX509Provider(beanFactory); injectUserDetailsServiceIntoOpenIDProvider(beanFactory); - injectAuthenticationEntryPointIntoExceptionTranslationFilter(beanFactory); - - injectRememberMeServicesIntoFiltersRequiringIt(beanFactory); - - configureFilterChain(beanFactory); } private void injectUserDetailsServiceIntoRememberMeServices(ConfigurableListableBeanFactory bf) { @@ -80,10 +70,10 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor PropertyValue pv = x509AuthProvider.getPropertyValues().getPropertyValue("preAuthenticatedUserDetailsService"); if (pv == null) { - UserDetailsByNameServiceWrapper preAuthUserService = new UserDetailsByNameServiceWrapper(); - preAuthUserService.setUserDetailsService(ConfigUtils.getUserDetailsService(bf)); + BeanDefinitionBuilder preAuthUserService = BeanDefinitionBuilder.rootBeanDefinition(UserDetailsByNameServiceWrapper.class); + preAuthUserService.addPropertyValue("userDetailsService", ConfigUtils.getUserDetailsService(bf)); x509AuthProvider.getPropertyValues().addPropertyValue("preAuthenticatedUserDetailsService", - preAuthUserService); + preAuthUserService.getBeanDefinition()); } else { RootBeanDefinition preAuthUserService = (RootBeanDefinition) pv.getValue(); Object userService = @@ -130,156 +120,34 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor } /** - * Sets the remember-me services, if required, on any instances of AbstractProcessingFilter and - * BasicProcessingFilter. - */ - private void injectRememberMeServicesIntoFiltersRequiringIt(ConfigurableListableBeanFactory beanFactory) { - Map beans = beanFactory.getBeansOfType(RememberMeServices.class); - - RememberMeServices rememberMeServices = null; - - if(beans.size() == 0) { - logger.debug("No RememberMeServices configured"); - return; - } - - if (beans.size() == 1) { - rememberMeServices = (RememberMeServices) beans.values().toArray()[0]; - } else { - throw new SecurityConfigurationException("More than one RememberMeServices bean found."); - } - - if (rememberMeServices == null) { - return; - } - - // Address AbstractProcessingFilter instances - Iterator filters = beanFactory.getBeansOfType(AbstractProcessingFilter.class).values().iterator(); - - while (filters.hasNext()) { - AbstractProcessingFilter filter = (AbstractProcessingFilter) filters.next(); - - logger.info("Using RememberMeServices " + rememberMeServices + " with filter " + filter); - filter.setRememberMeServices(rememberMeServices); - } - - // Address BasicProcessingFilter instance, if it exists - // NB: For remember-me to be sent back, a user must submit a "_spring_security_remember_me" with their login request. - // Most of the time a user won't present such a parameter with their BASIC authentication request. - // In the future we might support setting the AbstractRememberMeServices.alwaysRemember = true, but I am reluctant to - // do so because it seems likely to lead to lower security for 99.99% of users if they set the property to true. - if (beanFactory.containsBean(BeanIds.BASIC_AUTHENTICATION_FILTER)) { - BasicProcessingFilter filter = (BasicProcessingFilter) beanFactory.getBean(BeanIds.BASIC_AUTHENTICATION_FILTER); - - logger.info("Using RememberMeServices " + rememberMeServices + " with filter " + filter); - filter.setRememberMeServices(rememberMeServices); - } - } - - /** - * Selects the entry point that should be used in ExceptionTranslationFilter. Strategy is - * - *
    - *
  1. If only one, use that one.
  2. - *
  3. If more than one, use the form login entry point (if form login is being used), then try basic
  4. - *
  5. If still null, throw an exception (for now).
  6. - *
+ * Selects the entry point that should be used in ExceptionTranslationFilter. If an entry point has been + * set during parsing of form, openID and basic authentication information, or via a custom reference + * (using custom-entry-point, then that will be used. Otherwise there + * must be a single entry point bean and that will be used. + * + * Todo: this could probably be more easily be done in a BeanPostProcessor for ExceptionTranslationFilter. * */ private void injectAuthenticationEntryPointIntoExceptionTranslationFilter(ConfigurableListableBeanFactory beanFactory) { - logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter"); - + logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter"); + BeanDefinition etf = beanFactory.getBeanDefinition(BeanIds.EXCEPTION_TRANSLATION_FILTER); - Map entryPointMap = beanFactory.getBeansOfType(AuthenticationEntryPoint.class); - List entryPoints = new ArrayList(entryPointMap.values()); - - Assert.isTrue(entryPoints.size() > 0, "No AuthenticationEntryPoint instances defined"); - - AuthenticationEntryPoint mainEntryPoint; - - if (entryPoints.size() == 1) { - mainEntryPoint = (AuthenticationEntryPoint) entryPoints.get(0); + + String entryPoint = null; + + if (beanFactory.containsBean(BeanIds.MAIN_ENTRY_POINT)) { + entryPoint = BeanIds.MAIN_ENTRY_POINT; + logger.info("Using main configured AuthenticationEntryPoint set to " + BeanIds.MAIN_ENTRY_POINT); } else { - mainEntryPoint = (AuthenticationEntryPoint) beanFactory.getBean(BeanIds.MAIN_ENTRY_POINT); - - if (mainEntryPoint == null) { - mainEntryPoint = (AuthenticationEntryPoint) entryPointMap.get(BeanIds.FORM_LOGIN_ENTRY_POINT); - } - - if (mainEntryPoint == null) { - mainEntryPoint = (AuthenticationEntryPoint) entryPointMap.get(BeanIds.BASIC_AUTHENTICATION_ENTRY_POINT); - if (mainEntryPoint == null) { - throw new SecurityConfigurationException("Failed to resolve authentication entry point"); - } - } + String[] entryPoints = beanFactory.getBeanNamesForType(AuthenticationEntryPoint.class); + Assert.isTrue(entryPoints.length != 0, "No AuthenticationEntryPoint instances defined"); + Assert.isTrue(entryPoints.length == 1, "More than one AuthenticationEntryPoint defined in context"); + entryPoint = entryPoints[0]; } - - logger.info("Main AuthenticationEntryPoint set to " + mainEntryPoint); - - etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", mainEntryPoint); - } - - private void configureFilterChain(ConfigurableListableBeanFactory beanFactory) { - FilterChainProxy filterChainProxy = - (FilterChainProxy) beanFactory.getBean(BeanIds.FILTER_CHAIN_PROXY); - // Set the default match - List defaultFilterChain = orderFilters(beanFactory); - - // Note that this returns a copy - Map filterMap = filterChainProxy.getFilterChainMap(); - - String allUrlsMatch = filterChainProxy.getMatcher().getUniversalMatchPattern(); - - filterMap.put(allUrlsMatch, defaultFilterChain); - - filterChainProxy.setFilterChainMap(filterMap); - - Map sessionFilters = beanFactory.getBeansOfType(ConcurrentSessionFilter.class); - - if (!sessionFilters.isEmpty()) { - logger.info("Concurrent session filter in use, setting 'forceEagerSessionCreation' to true"); - HttpSessionContextIntegrationFilter scif = (HttpSessionContextIntegrationFilter) - beanFactory.getBean(BeanIds.HTTP_SESSION_CONTEXT_INTEGRATION_FILTER); - scif.setForceEagerSessionCreation(true); - } - - logger.info("Configured filter chain(s): " + filterChainProxy); - } - - private List orderFilters(ConfigurableListableBeanFactory beanFactory) { - Map filters = beanFactory.getBeansOfType(Filter.class); - - Assert.notEmpty(filters, "No filters found in app context!"); - - Iterator ids = filters.keySet().iterator(); - - List orderedFilters = new ArrayList(); - - while (ids.hasNext()) { - String id = (String) ids.next(); - Filter filter = (Filter) filters.get(id); - - if (filter instanceof FilterChainProxy) { - continue; - } - - // Filters must be Spring security filters or wrapped using - if (!filter.getClass().getName().startsWith("org.springframework.security")) { - continue; - } - - if (!(filter instanceof Ordered)) { - logger.info("Filter " + id + " doesn't implement the Ordered interface, skipping it."); - continue; - } - - orderedFilters.add(filter); - } - - Collections.sort(orderedFilters, new OrderComparator()); - - return orderedFilters; + + logger.info("Using bean '" + entryPoint + "' as the entry point."); + etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", new RuntimeBeanReference(entryPoint)); } public int getOrder() { diff --git a/core/src/main/java/org/springframework/security/config/RememberMeServicesInjectionBeanPostProcessor.java b/core/src/main/java/org/springframework/security/config/RememberMeServicesInjectionBeanPostProcessor.java new file mode 100644 index 0000000000..9768a81107 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/RememberMeServicesInjectionBeanPostProcessor.java @@ -0,0 +1,66 @@ +package org.springframework.security.config; + +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.ListableBeanFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.security.ui.AbstractProcessingFilter; +import org.springframework.security.ui.basicauth.BasicProcessingFilter; +import org.springframework.security.ui.rememberme.RememberMeServices; +import org.springframework.util.Assert; + +/** + * + * @author Luke Taylor + * @version $Id$ + * @since 2.0 + */ +public class RememberMeServicesInjectionBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { + private Log logger = LogFactory.getLog(getClass()); + + private ListableBeanFactory beanFactory; + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof AbstractProcessingFilter) { + AbstractProcessingFilter pf = (AbstractProcessingFilter) bean; + + if (pf.getRememberMeServices() == null) { + logger.info("Setting RememberMeServices on bean " + beanName); + pf.setRememberMeServices(getRememberMeServices()); + } + } else if (beanName.equals(BeanIds.BASIC_AUTHENTICATION_FILTER)) { + // NB: For remember-me to be sent back, a user must submit a "_spring_security_remember_me" with their login request. + // Most of the time a user won't present such a parameter with their BASIC authentication request. + // In the future we might support setting the AbstractRememberMeServices.alwaysRemember = true, but I am reluctant to + // do so because it seems likely to lead to lower security for 99.99% of users if they set the property to true. + + BasicProcessingFilter bf = (BasicProcessingFilter) bean; + logger.info("Setting RememberMeServices on bean " + beanName); + bf.setRememberMeServices(getRememberMeServices()); + } + + return bean; + } + + private RememberMeServices getRememberMeServices() { + Map beans = beanFactory.getBeansOfType(RememberMeServices.class); + + Assert.isTrue(beans.size() > 0, "No RememberMeServices configured"); + Assert.isTrue(beans.size() == 1, "More than one RememberMeServices bean found."); + + return (RememberMeServices) beans.values().toArray()[0]; + } + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public void setBeanFactory(BeanFactory beanFactory) throws BeansException { + this.beanFactory = (ListableBeanFactory) beanFactory; + } +} diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java index 529fd54feb..99119f979c 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -345,7 +345,27 @@ public class HttpSecurityBeanDefinitionParserTests { List filters = getFilterChainProxy().getFilters("/someurl"); assertFalse(filters.get(1) instanceof SessionFixationProtectionFilter); - } + } + + /** + * See SEC-750. If the http security post processor causes beans to be instantiated too eagerly, they way miss + * additional processing. In this method we have a UserDetailsService which is referenced from the namespace + * and also has a post processor registered which will modify it. + */ + @Test + public void httpElementDoesntInterfereWithBeanPostProcessing() { + setContext( + "" + + "" + + "" + + "" + ); + + PostProcessedMockUserDetailsService service = (PostProcessedMockUserDetailsService)appContext.getBean("myUserService"); + + assertEquals("Hello from the post processor!", service.getPostProcessorWasHere()); + } + private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context); diff --git a/core/src/test/java/org/springframework/security/config/MockUserServiceBeanPostProcessor.java b/core/src/test/java/org/springframework/security/config/MockUserServiceBeanPostProcessor.java new file mode 100644 index 0000000000..ccdb78007e --- /dev/null +++ b/core/src/test/java/org/springframework/security/config/MockUserServiceBeanPostProcessor.java @@ -0,0 +1,25 @@ +package org.springframework.security.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * Test bean post processor which injects a message into a PostProcessedMockUserDetailsService. + * + * @author Luke Taylor + * @version $Id$ + */ +public class MockUserServiceBeanPostProcessor implements BeanPostProcessor { + + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof PostProcessedMockUserDetailsService) { + ((PostProcessedMockUserDetailsService)bean).setPostProcessorWasHere("Hello from the post processor!"); + } + + return bean; + } +} diff --git a/core/src/test/java/org/springframework/security/config/PostProcessedMockUserDetailsService.java b/core/src/test/java/org/springframework/security/config/PostProcessedMockUserDetailsService.java new file mode 100644 index 0000000000..ead48eac09 --- /dev/null +++ b/core/src/test/java/org/springframework/security/config/PostProcessedMockUserDetailsService.java @@ -0,0 +1,27 @@ +package org.springframework.security.config; + +import org.springframework.dao.DataAccessException; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.security.userdetails.UserDetailsService; +import org.springframework.security.userdetails.UsernameNotFoundException; + +public class PostProcessedMockUserDetailsService implements UserDetailsService { + private String postProcessorWasHere; + + public PostProcessedMockUserDetailsService() { + this.postProcessorWasHere = "Post processor hasn't been yet"; + } + + public String getPostProcessorWasHere() { + return postProcessorWasHere; + } + + public void setPostProcessorWasHere(String postProcessorWasHere) { + this.postProcessorWasHere = postProcessorWasHere; + } + + public UserDetails loadUserByUsername(String username) + throws UsernameNotFoundException, DataAccessException { + throw new UnsupportedOperationException("Not for actual use"); + } +}