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_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>