diff --git a/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java new file mode 100644 index 0000000000..d744b77d96 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java @@ -0,0 +1,70 @@ +package org.springframework.security.config; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.BeanDefinitionBuilder; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.beans.factory.xml.BeanDefinitionParser; +import org.springframework.beans.factory.xml.ParserContext; +import org.springframework.security.concurrent.ConcurrentSessionControllerImpl; +import org.springframework.security.concurrent.ConcurrentSessionFilter; +import org.springframework.security.concurrent.SessionRegistryImpl; +import org.springframework.security.providers.ProviderManager; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * Sets up support for concurrent session support control, creating {@link ConcurrentSessionFilter}, + * {@link SessionRegistryImpl} and {@link ConcurrentSessionControllerImpl}. The session controller is also registered + * with the default {@link ProviderManager} (which is automatically registered during namespace configuration). + * + * @author Luke Taylor + * @version $Id$ + */ +public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionParser { + static final String DEFAULT_SESSION_REGISTRY_ID = "_sessionRegistry"; + static final String DEFAULT_CONCURRENT_SESSION_FILTER_ID = "_concurrentSessionFilter"; + static final String DEFAULT_SESSION_CONTROLLER_ID = "_concurrentSessionController"; + + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionRegistry beanRegistry = parserContext.getRegistry(); + + RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class); + BeanDefinitionBuilder filterBuilder = + BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class); + BeanDefinitionBuilder controllerBuilder + = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class); + controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(DEFAULT_SESSION_REGISTRY_ID)); + filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(DEFAULT_SESSION_REGISTRY_ID)); + + String expiryUrl = element.getAttribute("expiryUrl"); + + if (StringUtils.hasText(expiryUrl)) { + filterBuilder.addPropertyValue("expiryUrl", expiryUrl); + } + + String maxSessions = element.getAttribute("maxSessions"); + + if (StringUtils.hasText(expiryUrl)) { + controllerBuilder.addPropertyValue("maximumSessions", maxSessions); + } + + String exceptionIfMaximumExceeded = element.getAttribute("exceptionIfMaximumExceeded"); + + if (StringUtils.hasText(expiryUrl)) { + controllerBuilder.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded); + } + + BeanDefinition controller = controllerBuilder.getBeanDefinition(); + beanRegistry.registerBeanDefinition(DEFAULT_SESSION_REGISTRY_ID, sessionRegistry); + beanRegistry.registerBeanDefinition(DEFAULT_SESSION_CONTROLLER_ID, controller); + beanRegistry.registerBeanDefinition(DEFAULT_CONCURRENT_SESSION_FILTER_ID, filterBuilder.getBeanDefinition()); + + BeanDefinition providerManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext); + + providerManager.getPropertyValues().addPropertyValue("sessionController", controller); + + return null; + } +} 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 464314dd55..c4f3952ce4 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -40,9 +40,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { public static final String DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID = "_exceptionTranslationFilter"; public static final String DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID = "_filterSecurityInterceptor"; + public static final String CONCURRENT_SESSIONS_ELEMENT = "concurrent-session-control"; public static final String LOGOUT_ELEMENT = "logout"; public static final String FORM_LOGIN_ELEMENT = "form-login"; - public static final String BASIC_AUTH_ELEMENT = "http-basic"; + public static final String BASIC_AUTH_ELEMENT = "http-basic"; static final String PATH_PATTERN_ATTRIBUTE = "pattern"; static final String PATTERN_TYPE_ATTRIBUTE = "pathType"; @@ -92,7 +93,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"), filterChainMap, interceptorFilterInvDefSource); - // TODO: if empty, set a default set a default /**, omitting login url + + Element sessionControlElt = DomUtils.getChildElementByTagName(element, CONCURRENT_SESSIONS_ELEMENT); + + if (sessionControlElt != null) { + new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext); + } BeanDefinitionRegistry registry = parserContext.getRegistry(); @@ -112,7 +118,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { if (basicAuthElt != null) { new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext); - } + } registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy); registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF); diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc index 2bbc31d6dd..79e2e8f300 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc @@ -1,16 +1,17 @@ namespace beans = "http://www.springframework.org/schema/beans" namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0" +namespace security = "http://www.springframework.org/schema/security" datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes" -#default namespace = "http://www.springframework.org/schema/security" +default namespace = "http://www.springframework.org/schema/security" + +# targetNamespace="http://www.springframework.org/schema/security" path-type = ## Defines the type types of pattern used to specify URL paths. Defaults to "ant" [ a:defaultValue = "ant" ] attribute pathType {"regex" | "ant"} - - autoconfig = ## Provides automatic security configration for a application element autoconfig {autoconfig.attlist, empty} @@ -47,7 +48,7 @@ protect.attlist &= http = ## Container element for HTTP security configuration - element http {http.attlist, intercept-url+, logout?, form-login?, http-basic? } + element http {http.attlist, intercept-url+, form-login?, http-basic?, logout?, concurrent-session-control? } http.attlist &= ## Controls the eagerness with which an HTTP session is created. [ a:defaultValue = "ifRequired" ] attribute createSession {"ifRequired" | "always" | "never" }? @@ -57,6 +58,11 @@ http.attlist &= http.attlist &= ## Whether test URLs should be converted to lower case prior to comparing with defined path patterns. [ a:defaultValue = "true" ] attribute lowerCaseComparisons {"true" | "false"}? +http.attlist &= + ## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be + ## used for authorizing HTTP requests. + attribute accessDecisionManager {xsd:string}? + intercept-url = ## Specifies the access attributes and/or filter list for a particular set of URLs. @@ -72,6 +78,9 @@ intercept-url.attlist &= ## The full filter stack (consisting of all defined filters, will be applied to any other paths). intercept-url.attlist &= attribute filters {"none"}? +intercept-url.attlist &= + ## Used to specify that a URL must be accessed over http or https + attribute requiresChannel {"http" | "https"}? logout = element logout {logout.attlist, empty} @@ -115,13 +124,16 @@ http-basic = http-basic.attlist &= attribute realm {xsd:string} -concurrent-sessions = +concurrent-session-control = ## Adds support for concurrent session control, allowing limits to be placed on the number of sessions a ## user can have. - element concurrent-sessions {concurrent-sessions.attlist, empty} + element concurrent-session-control {concurrent-sessions.attlist, empty} concurrent-sessions.attlist &= attribute maxSessions {xsd:positiveInteger}? - +concurrent-sessions.attlist &= + attribute expiredUrl {xsd:string}? +concurrent-sessions.attlist &= + attribute exceptionIfMaximumExceeded {"true" | "false"}? authentication-provider = element authentication-provider {authentication-provider.attlist, (user-service | jdbc-user-service)} diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd index 1464a61dbf..adfb1a6b03 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd @@ -1,10 +1,6 @@ - - + + @@ -29,7 +25,7 @@ Sets up an ldap authentication provider, optionally with an embedded ldap server - + @@ -47,7 +43,7 @@ - + @@ -56,7 +52,7 @@ Defines a protected method and the access control configuration attributes that apply to it - + @@ -77,12 +73,13 @@ - - - - + + + + + - + @@ -120,13 +117,14 @@ + Specifies the access attributes and/or filter list for a particular set of URLs. - + @@ -143,10 +141,21 @@ + + + Used to specify that a URL must be accessed over http or https + + + + + + + + - + @@ -166,7 +175,7 @@ Sets up a form login configuration - + @@ -187,17 +196,17 @@ - + - + - + - + @@ -209,35 +218,44 @@ Adds support for basic authentication - + - + - + + + + + + + + + + - - + + - - + + - + @@ -245,7 +263,7 @@ - + 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 aa45a57e3f..d34faaa6a6 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.security.concurrent.ConcurrentSessionFilter; import org.springframework.security.context.HttpSessionContextIntegrationFilter; import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.ui.ExceptionTranslationFilter; @@ -52,11 +53,12 @@ public class HttpSecurityBeanDefinitionParserTests { (FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID); List filterList = filterChainProxy.getFilters("/someurl"); - - assertTrue("Expected 7 filterList in chain", filterList.size() == 7); + + assertTrue("Expected 8 filters in chain", filterList.size() == 8); Iterator filters = filterList.iterator(); + assertTrue(filters.next() instanceof ConcurrentSessionFilter); assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter); assertTrue(filters.next() instanceof LogoutFilter); assertTrue(filters.next() instanceof AuthenticationProcessingFilter); diff --git a/core/src/test/resources/org/springframework/security/config/http-security.xml b/core/src/test/resources/org/springframework/security/config/http-security.xml index 15b921937f..ba978e5ff5 100644 --- a/core/src/test/resources/org/springframework/security/config/http-security.xml +++ b/core/src/test/resources/org/springframework/security/config/http-security.xml @@ -11,14 +11,16 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - - - + + + + + diff --git a/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml b/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml index 16cdb6621e..9134b5d509 100644 --- a/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml +++ b/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml @@ -12,16 +12,15 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd"> - - - + - + + diff --git a/samples/tutorial/src/main/webapp/WEB-INF/web.xml b/samples/tutorial/src/main/webapp/WEB-INF/web.xml index 06313f2841..3c3618fc66 100644 --- a/samples/tutorial/src/main/webapp/WEB-INF/web.xml +++ b/samples/tutorial/src/main/webapp/WEB-INF/web.xml @@ -47,7 +47,15 @@ org.springframework.web.context.ContextLoaderListener - + + + org.springframework.security.ui.session.HttpSessionEventPublisher + + + index.jsp