Browse Source

SEC-583: Implementation of namespace config for concurrent session support.

Also some minor adjustments to ordering of different http features in schema.
Luke Taylor 18 năm trước cách đây
mục cha
commit
d3b165749f

+ 70 - 0
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;
+    }
+}

+ 9 - 3
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_EXCEPTION_TRANSLATION_FILTER_ID = "_exceptionTranslationFilter";
     public static final String DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID = "_filterSecurityInterceptor";
     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 LOGOUT_ELEMENT = "logout";
     public static final String FORM_LOGIN_ELEMENT = "form-login";
     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 PATH_PATTERN_ATTRIBUTE = "pattern";
     static final String PATTERN_TYPE_ATTRIBUTE = "pathType";
     static final String PATTERN_TYPE_ATTRIBUTE = "pathType";
@@ -92,7 +93,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
 
 
         parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
         parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
                 filterChainMap, interceptorFilterInvDefSource);
                 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();
         BeanDefinitionRegistry registry = parserContext.getRegistry();
 
 
@@ -112,7 +118,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
 
 
         if (basicAuthElt != null) {
         if (basicAuthElt != null) {
             new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext);
             new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext);
-        }        
+        }
 
 
         registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy);
         registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy);
         registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF);
         registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF);

+ 19 - 7
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 beans = "http://www.springframework.org/schema/beans"
 namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
 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"
 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 =
 path-type =
     ## Defines the type types of pattern used to specify URL paths. Defaults to "ant"
     ## Defines the type types of pattern used to specify URL paths. Defaults to "ant"
     [ a:defaultValue = "ant" ] attribute pathType {"regex" | "ant"}
     [ a:defaultValue = "ant" ] attribute pathType {"regex" | "ant"}
 
 
-
-
 autoconfig = 
 autoconfig = 
     ## Provides automatic security configration for a application
     ## Provides automatic security configration for a application
     element autoconfig {autoconfig.attlist, empty}
     element autoconfig {autoconfig.attlist, empty}
@@ -47,7 +48,7 @@ protect.attlist &=
 
 
 http =
 http =
     ## Container element for HTTP security configuration
     ## 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 &=
 http.attlist &=
     ## Controls the eagerness with which an HTTP session is created.
     ## Controls the eagerness with which an HTTP session is created.
     [ a:defaultValue = "ifRequired" ] attribute createSession {"ifRequired" | "always" | "never" }?
     [ a:defaultValue = "ifRequired" ] attribute createSession {"ifRequired" | "always" | "never" }?
@@ -57,6 +58,11 @@ http.attlist &=
 http.attlist &=
 http.attlist &=
     ## Whether test URLs should be converted to lower case prior to comparing with defined path patterns.
     ## Whether test URLs should be converted to lower case prior to comparing with defined path patterns.
     [ a:defaultValue = "true" ] attribute lowerCaseComparisons {"true" | "false"}?
     [ 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 =
 intercept-url =
     ## Specifies the access attributes and/or filter list for a particular set of URLs.
     ## 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).
     ## The full filter stack (consisting of all defined filters, will be applied to any other paths).
 intercept-url.attlist &=
 intercept-url.attlist &=
     attribute filters {"none"}?
     attribute filters {"none"}?
+intercept-url.attlist &=
+    ## Used to specify that a URL must be accessed over http or https
+    attribute requiresChannel {"http" | "https"}?
 
 
 logout =
 logout =
     element logout {logout.attlist, empty}
     element logout {logout.attlist, empty}
@@ -115,13 +124,16 @@ http-basic =
 http-basic.attlist &= 
 http-basic.attlist &= 
     attribute realm {xsd:string}
     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
     ## Adds support for concurrent session control, allowing limits to be placed on the number of sessions a
     ## user can have.
     ## user can have.
-    element concurrent-sessions {concurrent-sessions.attlist, empty}
+    element concurrent-session-control {concurrent-sessions.attlist, empty}
 concurrent-sessions.attlist &=
 concurrent-sessions.attlist &=
     attribute maxSessions {xsd:positiveInteger}?
     attribute maxSessions {xsd:positiveInteger}?
-
+concurrent-sessions.attlist &=
+    attribute expiredUrl {xsd:string}?
+concurrent-sessions.attlist &=
+    attribute exceptionIfMaximumExceeded {"true" | "false"}?
 
 
 authentication-provider =
 authentication-provider =
     element authentication-provider {authentication-provider.attlist, (user-service | jdbc-user-service)}
     element authentication-provider {authentication-provider.attlist, (user-service | jdbc-user-service)}

+ 48 - 30
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

@@ -1,10 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
-<xs:schema xmlns="http://www.springframework.org/schema/security"
-  xmlns:xs="http://www.w3.org/2001/XMLSchema"
-  targetNamespace="http://www.springframework.org/schema/security"
-  xmlns:beans="http://www.springframework.org/schema/beans"
-  elementFormDefault="qualified" attributeFormDefault="unqualified">
-  <!-- default namespace = "http://www.springframework.org/schema/security" -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.springframework.org/schema/security" xmlns:security="http://www.springframework.org/schema/security">
+  <!-- targetNamespace="http://www.springframework.org/schema/security" -->
   <xs:attributeGroup name="path-type">
   <xs:attributeGroup name="path-type">
     <xs:attribute name="pathType" use="required">
     <xs:attribute name="pathType" use="required">
       <xs:annotation>
       <xs:annotation>
@@ -29,7 +25,7 @@
       <xs:documentation>Sets up an ldap authentication provider, optionally with an embedded ldap server</xs:documentation>
       <xs:documentation>Sets up an ldap authentication provider, optionally with an embedded ldap server</xs:documentation>
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="ldap.attlist"/>
+      <xs:attributeGroup ref="security:ldap.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="ldap.attlist">
   <xs:attributeGroup name="ldap.attlist">
@@ -47,7 +43,7 @@
   <xs:element name="intercept-methods">
   <xs:element name="intercept-methods">
     <xs:complexType>
     <xs:complexType>
       <xs:sequence>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="protect"/>
+        <xs:element maxOccurs="unbounded" ref="security:protect"/>
       </xs:sequence>
       </xs:sequence>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
@@ -56,7 +52,7 @@
       <xs:documentation>Defines a protected method and the access control configuration attributes that apply to it</xs:documentation>
       <xs:documentation>Defines a protected method and the access control configuration attributes that apply to it</xs:documentation>
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="protect.attlist"/>
+      <xs:attributeGroup ref="security:protect.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="protect.attlist">
   <xs:attributeGroup name="protect.attlist">
@@ -77,12 +73,13 @@
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
       <xs:sequence>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="intercept-url"/>
-        <xs:element minOccurs="0" ref="logout"/>
-        <xs:element minOccurs="0" ref="form-login"/>
-        <xs:element minOccurs="0" ref="http-basic"/>
+        <xs:element maxOccurs="unbounded" ref="security:intercept-url"/>
+        <xs:element minOccurs="0" ref="security:form-login"/>
+        <xs:element minOccurs="0" ref="security:http-basic"/>
+        <xs:element minOccurs="0" ref="security:logout"/>
+        <xs:element minOccurs="0" ref="security:concurrent-session-control"/>
       </xs:sequence>
       </xs:sequence>
-      <xs:attributeGroup ref="http.attlist"/>
+      <xs:attributeGroup ref="security:http.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="http.attlist">
   <xs:attributeGroup name="http.attlist">
@@ -120,13 +117,14 @@
         </xs:restriction>
         </xs:restriction>
       </xs:simpleType>
       </xs:simpleType>
     </xs:attribute>
     </xs:attribute>
+    <xs:attribute name="accessDecisionManager" type="xs:string"/>
   </xs:attributeGroup>
   </xs:attributeGroup>
   <xs:element name="intercept-url">
   <xs:element name="intercept-url">
     <xs:annotation>
     <xs:annotation>
       <xs:documentation>Specifies the access attributes and/or filter list for a particular set of URLs.</xs:documentation>
       <xs:documentation>Specifies the access attributes and/or filter list for a particular set of URLs.</xs:documentation>
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="intercept-url.attlist"/>
+      <xs:attributeGroup ref="security:intercept-url.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="intercept-url.attlist">
   <xs:attributeGroup name="intercept-url.attlist">
@@ -143,10 +141,21 @@
         </xs:restriction>
         </xs:restriction>
       </xs:simpleType>
       </xs:simpleType>
     </xs:attribute>
     </xs:attribute>
+    <xs:attribute name="requiresChannel">
+      <xs:annotation>
+        <xs:documentation>Used to specify that a URL must be accessed over http or https</xs:documentation>
+      </xs:annotation>
+      <xs:simpleType>
+        <xs:restriction base="xs:token">
+          <xs:enumeration value="http"/>
+          <xs:enumeration value="https"/>
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:attribute>
   </xs:attributeGroup>
   </xs:attributeGroup>
   <xs:element name="logout">
   <xs:element name="logout">
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="logout.attlist"/>
+      <xs:attributeGroup ref="security:logout.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="logout.attlist">
   <xs:attributeGroup name="logout.attlist">
@@ -166,7 +175,7 @@
       <xs:documentation>Sets up a form login configuration</xs:documentation>
       <xs:documentation>Sets up a form login configuration</xs:documentation>
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="form-login.attlist"/>
+      <xs:attributeGroup ref="security:form-login.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="form-login.attlist">
   <xs:attributeGroup name="form-login.attlist">
@@ -187,17 +196,17 @@
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
       <xs:sequence>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="filter-chain"/>
+        <xs:element maxOccurs="unbounded" ref="security:filter-chain"/>
       </xs:sequence>
       </xs:sequence>
-      <xs:attributeGroup ref="filter-chain-map.attlist"/>
+      <xs:attributeGroup ref="security:filter-chain-map.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="filter-chain-map.attlist">
   <xs:attributeGroup name="filter-chain-map.attlist">
-    <xs:attributeGroup ref="path-type"/>
+    <xs:attributeGroup ref="security:path-type"/>
   </xs:attributeGroup>
   </xs:attributeGroup>
   <xs:element name="filter-chain">
   <xs:element name="filter-chain">
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="filter-chain.attlist"/>
+      <xs:attributeGroup ref="security:filter-chain.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="filter-chain.attlist">
   <xs:attributeGroup name="filter-chain.attlist">
@@ -209,35 +218,44 @@
       <xs:documentation>Adds support for basic authentication</xs:documentation>
       <xs:documentation>Adds support for basic authentication</xs:documentation>
     </xs:annotation>
     </xs:annotation>
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="http-basic.attlist"/>
+      <xs:attributeGroup ref="security:http-basic.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="http-basic.attlist">
   <xs:attributeGroup name="http-basic.attlist">
     <xs:attribute name="realm" use="required" type="xs:string"/>
     <xs:attribute name="realm" use="required" type="xs:string"/>
   </xs:attributeGroup>
   </xs:attributeGroup>
-  <xs:element name="concurrent-sessions">
+  <xs:element name="concurrent-session-control">
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="concurrent-sessions.attlist"/>
+      <xs:attributeGroup ref="security:concurrent-sessions.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="concurrent-sessions.attlist">
   <xs:attributeGroup name="concurrent-sessions.attlist">
     <xs:attribute name="maxSessions" type="xs:positiveInteger"/>
     <xs:attribute name="maxSessions" type="xs:positiveInteger"/>
+    <xs:attribute name="expiredUrl" type="xs:string"/>
+    <xs:attribute name="exceptionIfMaximumExceeded">
+      <xs:simpleType>
+        <xs:restriction base="xs:token">
+          <xs:enumeration value="true"/>
+          <xs:enumeration value="false"/>
+        </xs:restriction>
+      </xs:simpleType>
+    </xs:attribute>
   </xs:attributeGroup>
   </xs:attributeGroup>
   <xs:element name="authentication-provider">
   <xs:element name="authentication-provider">
     <xs:complexType>
     <xs:complexType>
       <xs:choice>
       <xs:choice>
-        <xs:element ref="user-service"/>
-        <xs:element ref="jdbc-user-service"/>
+        <xs:element ref="security:user-service"/>
+        <xs:element ref="security:jdbc-user-service"/>
       </xs:choice>
       </xs:choice>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:element name="user-service">
   <xs:element name="user-service">
     <xs:complexType>
     <xs:complexType>
       <xs:choice>
       <xs:choice>
-        <xs:element minOccurs="0" maxOccurs="unbounded" ref="user"/>
-        <xs:element ref="jdbc-user-service"/>
+        <xs:element minOccurs="0" maxOccurs="unbounded" ref="security:user"/>
+        <xs:element ref="security:jdbc-user-service"/>
       </xs:choice>
       </xs:choice>
-      <xs:attributeGroup ref="user-service.attlist"/>
+      <xs:attributeGroup ref="security:user-service.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="user-service.attlist">
   <xs:attributeGroup name="user-service.attlist">
@@ -245,7 +263,7 @@
   </xs:attributeGroup>
   </xs:attributeGroup>
   <xs:element name="user">
   <xs:element name="user">
     <xs:complexType>
     <xs:complexType>
-      <xs:attributeGroup ref="user.attlist"/>
+      <xs:attributeGroup ref="security:user.attlist"/>
     </xs:complexType>
     </xs:complexType>
   </xs:element>
   </xs:element>
   <xs:attributeGroup name="user.attlist">
   <xs:attributeGroup name="user.attlist">

+ 4 - 2
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.BeforeClass;
 import org.junit.Test;
 import org.junit.Test;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
 import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.security.concurrent.ConcurrentSessionFilter;
 import org.springframework.security.context.HttpSessionContextIntegrationFilter;
 import org.springframework.security.context.HttpSessionContextIntegrationFilter;
 import org.springframework.security.intercept.web.FilterSecurityInterceptor;
 import org.springframework.security.intercept.web.FilterSecurityInterceptor;
 import org.springframework.security.ui.ExceptionTranslationFilter;
 import org.springframework.security.ui.ExceptionTranslationFilter;
@@ -52,11 +53,12 @@ public class HttpSecurityBeanDefinitionParserTests {
                 (FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
                 (FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
 
 
         List filterList = filterChainProxy.getFilters("/someurl");
         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();
         Iterator filters = filterList.iterator();
 
 
+        assertTrue(filters.next() instanceof ConcurrentSessionFilter);
         assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
         assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
         assertTrue(filters.next() instanceof LogoutFilter);
         assertTrue(filters.next() instanceof LogoutFilter);
         assertTrue(filters.next() instanceof AuthenticationProcessingFilter);
         assertTrue(filters.next() instanceof AuthenticationProcessingFilter);

+ 5 - 3
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
         <security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
         <security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
         <security:intercept-url pattern="/**" access="ROLE_USER" />
         <security:intercept-url pattern="/**" access="ROLE_USER" />
 
 
-        <!-- Default logout configuration -->
-        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />        
-
         <!-- Default form login configuration. Will create filter and entry point -->
         <!-- Default form login configuration. Will create filter and entry point -->
         <security:form-login loginUrl="/j_spring_security_check"  />
         <security:form-login loginUrl="/j_spring_security_check"  />
 
 
         <!-- Default basic auth configuration. Will create filter and entry point -->
         <!-- Default basic auth configuration. Will create filter and entry point -->
         <security:http-basic realm="NamespaceTestRealm"  />
         <security:http-basic realm="NamespaceTestRealm"  />
+
+        <!-- Default logout configuration -->
+        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />        
+
+        <security:concurrent-session-control maxSessions="1"/>
     </security:http>
     </security:http>
 
 
     <security:authentication-provider>
     <security:authentication-provider>

+ 3 - 4
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
     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">
 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
 
 
-    <security:autoconfig />
-
-    <security:http>        
+    <security:http>
         <security:intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
         <security:intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
         <security:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_REMEMBERED" />
         <security:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_REMEMBERED" />
         <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
         <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
 
 
-        <security:logout />
         <security:form-login />
         <security:form-login />
         <security:http-basic realm="SpringSecurityTutorialApp"  />
         <security:http-basic realm="SpringSecurityTutorialApp"  />
+        <security:logout />
+        <security:concurrent-session-control maxSessions="1" exceptionIfMaximumExceeded="true"/>
 
 
     </security:http>
     </security:http>
 
 

+ 9 - 1
samples/tutorial/src/main/webapp/WEB-INF/web.xml

@@ -47,7 +47,15 @@
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 	</listener>
 	</listener>
 
 
- 	<welcome-file-list>
+	<!--
+	  - Publishes events for session creation and destruction through the application
+	  - context. Optional unless concurrent session control is being used.
+      -->
+    <listener>
+      <listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
+    </listener>
+
+     <welcome-file-list>
 		<welcome-file>index.jsp</welcome-file>
 		<welcome-file>index.jsp</welcome-file>
 	</welcome-file-list>
 	</welcome-file-list>