瀏覽代碼

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 年之前
父節點
當前提交
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_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);

+ 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 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)}

+ 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"?>
-<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:attribute name="pathType" use="required">
       <xs:annotation>
@@ -29,7 +25,7 @@
       <xs:documentation>Sets up an ldap authentication provider, optionally with an embedded ldap server</xs:documentation>
     </xs:annotation>
     <xs:complexType>
-      <xs:attributeGroup ref="ldap.attlist"/>
+      <xs:attributeGroup ref="security:ldap.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="ldap.attlist">
@@ -47,7 +43,7 @@
   <xs:element name="intercept-methods">
     <xs:complexType>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="protect"/>
+        <xs:element maxOccurs="unbounded" ref="security:protect"/>
       </xs:sequence>
     </xs:complexType>
   </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:annotation>
     <xs:complexType>
-      <xs:attributeGroup ref="protect.attlist"/>
+      <xs:attributeGroup ref="security:protect.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="protect.attlist">
@@ -77,12 +73,13 @@
     </xs:annotation>
     <xs:complexType>
       <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:attributeGroup ref="http.attlist"/>
+      <xs:attributeGroup ref="security:http.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="http.attlist">
@@ -120,13 +117,14 @@
         </xs:restriction>
       </xs:simpleType>
     </xs:attribute>
+    <xs:attribute name="accessDecisionManager" type="xs:string"/>
   </xs:attributeGroup>
   <xs:element name="intercept-url">
     <xs:annotation>
       <xs:documentation>Specifies the access attributes and/or filter list for a particular set of URLs.</xs:documentation>
     </xs:annotation>
     <xs:complexType>
-      <xs:attributeGroup ref="intercept-url.attlist"/>
+      <xs:attributeGroup ref="security:intercept-url.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="intercept-url.attlist">
@@ -143,10 +141,21 @@
         </xs:restriction>
       </xs:simpleType>
     </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:element name="logout">
     <xs:complexType>
-      <xs:attributeGroup ref="logout.attlist"/>
+      <xs:attributeGroup ref="security:logout.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="logout.attlist">
@@ -166,7 +175,7 @@
       <xs:documentation>Sets up a form login configuration</xs:documentation>
     </xs:annotation>
     <xs:complexType>
-      <xs:attributeGroup ref="form-login.attlist"/>
+      <xs:attributeGroup ref="security:form-login.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="form-login.attlist">
@@ -187,17 +196,17 @@
     </xs:annotation>
     <xs:complexType>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="filter-chain"/>
+        <xs:element maxOccurs="unbounded" ref="security:filter-chain"/>
       </xs:sequence>
-      <xs:attributeGroup ref="filter-chain-map.attlist"/>
+      <xs:attributeGroup ref="security:filter-chain-map.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="filter-chain-map.attlist">
-    <xs:attributeGroup ref="path-type"/>
+    <xs:attributeGroup ref="security:path-type"/>
   </xs:attributeGroup>
   <xs:element name="filter-chain">
     <xs:complexType>
-      <xs:attributeGroup ref="filter-chain.attlist"/>
+      <xs:attributeGroup ref="security:filter-chain.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="filter-chain.attlist">
@@ -209,35 +218,44 @@
       <xs:documentation>Adds support for basic authentication</xs:documentation>
     </xs:annotation>
     <xs:complexType>
-      <xs:attributeGroup ref="http-basic.attlist"/>
+      <xs:attributeGroup ref="security:http-basic.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="http-basic.attlist">
     <xs:attribute name="realm" use="required" type="xs:string"/>
   </xs:attributeGroup>
-  <xs:element name="concurrent-sessions">
+  <xs:element name="concurrent-session-control">
     <xs:complexType>
-      <xs:attributeGroup ref="concurrent-sessions.attlist"/>
+      <xs:attributeGroup ref="security:concurrent-sessions.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="concurrent-sessions.attlist">
     <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:element name="authentication-provider">
     <xs:complexType>
       <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:complexType>
   </xs:element>
   <xs:element name="user-service">
     <xs:complexType>
       <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:attributeGroup ref="user-service.attlist"/>
+      <xs:attributeGroup ref="security:user-service.attlist"/>
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="user-service.attlist">
@@ -245,7 +263,7 @@
   </xs:attributeGroup>
   <xs:element name="user">
     <xs:complexType>
-      <xs:attributeGroup ref="user.attlist"/>
+      <xs:attributeGroup ref="security:user.attlist"/>
     </xs:complexType>
   </xs:element>
   <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.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);

+ 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="/**" 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 -->
         <security:form-login loginUrl="/j_spring_security_check"  />
 
         <!-- Default basic auth configuration. Will create filter and entry point -->
         <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: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
 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/**" access="IS_AUTHENTICATED_REMEMBERED" />
         <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
 
-        <security:logout />
         <security:form-login />
         <security:http-basic realm="SpringSecurityTutorialApp"  />
+        <security:logout />
+        <security:concurrent-session-control maxSessions="1" exceptionIfMaximumExceeded="true"/>
 
     </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>
 
- 	<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-list>