diff --git a/core/src/main/java/org/springframework/security/config/AuthenticationManagerBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/AuthenticationManagerBeanDefinitionParser.java index e5ac443430..7176bbc3ca 100644 --- a/core/src/main/java/org/springframework/security/config/AuthenticationManagerBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/AuthenticationManagerBeanDefinitionParser.java @@ -3,27 +3,40 @@ package org.springframework.security.config; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.util.StringUtils; import org.w3c.dom.Element; /** - * Just registers an alias name for the default ProviderManager used by the namespace + * Registers an alias name for the default ProviderManager used by the namespace * configuration, allowing users to reference it in their beans and clearly see where the name is - * coming from. + * coming from. Also allows the ConcurrentSessionController to be set on the ProviderManager. * * @author Luke Taylor * @version $Id$ */ public class AuthenticationManagerBeanDefinitionParser implements BeanDefinitionParser { - private static final String ATT_ALIAS = "alias"; + private static final String ATT_SESSION_CONTROLLER_REF = "session-controller-ref"; + private static final String ATT_ALIAS = "alias"; public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext); + String alias = element.getAttribute(ATT_ALIAS); if (!StringUtils.hasText(alias)) { parserContext.getReaderContext().error(ATT_ALIAS + " is required.", element ); } + + String sessionControllerRef = element.getAttribute(ATT_SESSION_CONTROLLER_REF); + + if (StringUtils.hasText(sessionControllerRef)) { + ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext, + BeanIds.CONCURRENT_SESSION_CONTROLLER, element); + authManager.getPropertyValues().addPropertyValue("sessionController", + new RuntimeBeanReference(sessionControllerRef)); + } parserContext.getRegistry().registerAlias(BeanIds.AUTHENTICATION_MANAGER, alias); diff --git a/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java index ce6183813c..c9638e322f 100644 --- a/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java @@ -29,7 +29,8 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar static final String ATT_EXPIRY_URL = "expired-url"; static final String ATT_MAX_SESSIONS = "max-sessions"; static final String ATT_EXCEPTION_IF_MAX_EXCEEDED = "exception-if-maximum-exceeded"; - static final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias"; + static final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias"; + static final String ATT_SESSION_REGISTRY_REF = "session-registry-ref"; public BeanDefinition parse(Element element, ParserContext parserContext) { CompositeComponentDefinition compositeDef = @@ -38,26 +39,43 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar BeanDefinitionRegistry beanRegistry = parserContext.getRegistry(); - RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class); + String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF); + + if (!StringUtils.hasText(sessionRegistryId)) { + RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class); + beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry); + parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY)); + sessionRegistryId = BeanIds.SESSION_REGISTRY; + } else { + // Register the default ID as an alias so that things like session fixation filter can access it + beanRegistry.registerAlias(sessionRegistryId, BeanIds.SESSION_REGISTRY); + } + + String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS); + if (StringUtils.hasText(registryAlias)) { + beanRegistry.registerAlias(sessionRegistryId, registryAlias); + } + BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class); - BeanDefinitionBuilder controllerBuilder - = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class); - controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY)); - filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY)); + filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId)); Object source = parserContext.extractSource(element); filterBuilder.setSource(source); filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - controllerBuilder.setSource(source); - controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - + String expiryUrl = element.getAttribute(ATT_EXPIRY_URL); if (StringUtils.hasText(expiryUrl)) { ConfigUtils.validateHttpRedirect(expiryUrl, parserContext, source); filterBuilder.addPropertyValue("expiredUrl", expiryUrl); - } + } + + BeanDefinitionBuilder controllerBuilder + = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class); + controllerBuilder.setSource(source); + controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId)); String maxSessions = element.getAttribute(ATT_MAX_SESSIONS); @@ -72,23 +90,14 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar } BeanDefinition controller = controllerBuilder.getBeanDefinition(); - beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry); - parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY)); - String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS); - if (StringUtils.hasText(registryAlias)) { - beanRegistry.registerAlias(BeanIds.SESSION_REGISTRY, registryAlias); - } - beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_CONTROLLER, controller); parserContext.registerComponent(new BeanComponentDefinition(controller, BeanIds.CONCURRENT_SESSION_CONTROLLER)); beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_FILTER, filterBuilder.getBeanDefinition()); parserContext.registerComponent(new BeanComponentDefinition(filterBuilder.getBeanDefinition(), BeanIds.CONCURRENT_SESSION_FILTER)); ConfigUtils.addHttpFilter(parserContext, new RuntimeBeanReference(BeanIds.CONCURRENT_SESSION_FILTER)); - BeanDefinition providerManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext); - - providerManager.getPropertyValues().addPropertyValue("sessionController", controller); + ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext, BeanIds.CONCURRENT_SESSION_CONTROLLER, element); parserContext.popAndRegisterContainingComponent(); 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 0ca3a48a71..7685f4988e 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -7,6 +7,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.MutablePropertyValues; +import org.springframework.beans.PropertyValue; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.RuntimeBeanReference; @@ -22,6 +23,7 @@ import org.springframework.security.vote.AffirmativeBased; import org.springframework.security.vote.AuthenticatedVoter; import org.springframework.security.vote.RoleVoter; import org.springframework.util.StringUtils; +import org.w3c.dom.Element; /** * Utility methods used internally by the Spring Security namespace configuration code. @@ -168,4 +170,17 @@ public abstract class ConfigUtils { } pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source); } + + static void setSessionControllerOnAuthenticationManager(ParserContext pc, String beanName, Element sourceElt) { + BeanDefinition authManager = registerProviderManagerIfNecessary(pc); + PropertyValue pv = authManager.getPropertyValues().getPropertyValue("sessionController"); + + if (pv != null && pv.getValue() != null) { + pc.getReaderContext().error("A session controller has already been set on the authentication manager. " + + "The element isn't compatible with a custom session controller", + pc.extractSource(sourceElt)); + } + + authManager.getPropertyValues().addPropertyValue("sessionController", new RuntimeBeanReference(beanName)); + } } diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc index d588171089..b07d514986 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc @@ -335,13 +335,17 @@ concurrent-session-control = concurrent-sessions.attlist &= attribute max-sessions {xsd:positiveInteger}? concurrent-sessions.attlist &= + ## The URL a user will be redirected to if they attempt to use a session which has been "expired" by the concurrent session controller. attribute expired-url {xsd:string}? concurrent-sessions.attlist &= + ## Specifies that an exception should be raised when a user attempts to login twice. The default behaviour is to expire the original session. attribute exception-if-maximum-exceeded {boolean}? concurrent-sessions.attlist &= ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration attribute session-registry-alias {xsd:string}? - +concurrent-sessions.attlist &= + ## A reference to an external SessionRegistry implementation which will be used in place of the standard one. + attribute session-registry-ref {xsd:string}? remember-me = ## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach. @@ -408,11 +412,15 @@ x509.attlist &= user-service-ref? authentication-manager = - ## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element simple allows you to define an alias to allow you to reference the authentication-manager in your own beans. + ## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element allows you to define an alias to allow you to reference the authentication-manager in your own beans. element authentication-manager {authman.attlist} - ## The alias you wish to use for the AuthenticationManager bean authman.attlist &= + ## The alias you wish to use for the AuthenticationManager bean attribute alias {xsd:ID} +authman.attlist &= + ## Allows the session controller to be set on the internal AuthenticationManager. This should not be used with the element + attribute session-controller-ref {xsd:string}? + authentication-provider = ## Indicates that the contained user-service should be used as an authentication source. diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd index 4d85045fd0..19e3a7fbfc 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd @@ -489,7 +489,16 @@ - + + + Defines a protected method and the access control configuration + attributes that apply to it. We strongly advise you NOT to mix "protect" declarations + with any services provided "global-method-security". + + + + + @@ -502,16 +511,6 @@ - - - Defines a protected method and the access control configuration attributes - that apply to it. We strongly advise you NOT to mix "protect" declarations with any services - provided "global-method-security". - - - - - @@ -1014,14 +1013,30 @@ - - + + + The URL a user will be redirected to if they attempt to use a session + which has been "expired" by the concurrent session controller. + + + + + Specifies that an exception should be raised when a user attempts to login + twice. The default behaviour is to expire the original session. + + Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration + + + A reference to an external SessionRegistry implementation which will be + used in place of the standard one. + + @@ -1130,8 +1145,8 @@ If you are using namespace configuration with Spring Security, an - AuthenticationManager will automatically be registered. This element simple allows you to - define an alias to allow you to reference the authentication-manager in your own beans. + AuthenticationManager will automatically be registered. This element allows you to define an + alias to allow you to reference the authentication-manager in your own beans. @@ -1139,11 +1154,19 @@ - - The alias you wish to use for the AuthenticationManager - bean - - + + + The alias you wish to use for the AuthenticationManager + bean + + + + + Allows the session controller to be set on the internal + AuthenticationManager. This should not be used with the <concurrent-session-control + /> element + + 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 8c62371a60..9b6e1c76e7 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -8,7 +8,6 @@ import java.util.List; import org.junit.After; import org.junit.Test; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; @@ -40,7 +39,6 @@ import org.springframework.security.ui.rememberme.NullRememberMeServices; import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices; import org.springframework.security.ui.rememberme.RememberMeProcessingFilter; import org.springframework.security.ui.rememberme.RememberMeServices; -import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices; import org.springframework.security.ui.webapp.AuthenticationProcessingFilter; import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; import org.springframework.security.util.FieldUtils; @@ -405,7 +403,7 @@ public class HttpSecurityBeanDefinitionParserTests { setContext( "" + " " + - "" + AUTH_PROVIDER_XML); + "" + AUTH_PROVIDER_XML); List filters = getFilters("/someurl"); assertTrue(filters.get(0) instanceof ConcurrentSessionFilter); @@ -413,6 +411,55 @@ public class HttpSecurityBeanDefinitionParserTests { assertNotNull(appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER)); } + @Test + public void externalSessionRegistryBeanIsConfiguredCorrectly() throws Exception { + setContext( + "" + + " " + + "" + + "" + + AUTH_PROVIDER_XML); + Object sessionRegistry = appContext.getBean("seshRegistry"); + Object sessionRegistryFromFilter = FieldUtils.getFieldValue( + appContext.getBean(BeanIds.CONCURRENT_SESSION_FILTER),"sessionRegistry"); + Object sessionRegistryFromController = FieldUtils.getFieldValue( + appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER),"sessionRegistry"); + Object sessionRegistryFromFixationFilter = FieldUtils.getFieldValue( + appContext.getBean(BeanIds.SESSION_FIXATION_PROTECTION_FILTER),"sessionRegistry"); + + assertSame(sessionRegistry, sessionRegistryFromFilter); + assertSame(sessionRegistry, sessionRegistryFromController); + assertSame(sessionRegistry, sessionRegistryFromFixationFilter); + } + + @Test(expected=BeanDefinitionParsingException.class) + public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean() throws Exception { + setContext( + "" + + "" + + " " + + "" + + "" + + " " + + " " + + " " + + "" + AUTH_PROVIDER_XML); + } + + @Test(expected=BeanDefinitionParsingException.class) + public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean2() throws Exception { + setContext( + "" + + " " + + "" + + "" + + " " + + " " + + " " + + "" + + "" + AUTH_PROVIDER_XML); + } + @Test(expected=ConcurrentLoginException.class) public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception { setContext(