Browse Source

SEC-738: Add session-registry-alias attribute to concurrent-session-control
http://jira.springframework.org/browse/SEC-738. Added this attribute. Also various bugfixes in handling of attribute names for concurrent session control.

Luke Taylor 17 years ago
parent
commit
512c64fb98

+ 18 - 6
core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java

@@ -23,6 +23,12 @@ import org.w3c.dom.Element;
  * @version $Id$
  */
 public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionParser {
+
+    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";    
+	
     public BeanDefinition parse(Element element, ParserContext parserContext) {
         BeanDefinitionRegistry beanRegistry = parserContext.getRegistry();
 
@@ -38,26 +44,32 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
         filterBuilder.setSource(source);
         controllerBuilder.setSource(source);
 
-        String expiryUrl = element.getAttribute("expiryUrl");
+        String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
 
         if (StringUtils.hasText(expiryUrl)) {
-            filterBuilder.addPropertyValue("expiryUrl", expiryUrl);
+            filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
         }
 
-        String maxSessions = element.getAttribute("maxSessions");
+        String maxSessions = element.getAttribute(ATT_MAX_SESSIONS);
 
-        if (StringUtils.hasText(expiryUrl)) {
+        if (StringUtils.hasText(maxSessions)) {
             controllerBuilder.addPropertyValue("maximumSessions", maxSessions);
         }
 
-        String exceptionIfMaximumExceeded = element.getAttribute("exceptionIfMaximumExceeded");
+        String exceptionIfMaximumExceeded = element.getAttribute(ATT_EXCEPTION_IF_MAX_EXCEEDED);
 
-        if (StringUtils.hasText(expiryUrl)) {
+        if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
             controllerBuilder.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
         }
 
         BeanDefinition controller = controllerBuilder.getBeanDefinition();
         beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry);
+        
+        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);
         beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_FILTER, filterBuilder.getBeanDefinition());
 

+ 1 - 1
core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java

@@ -90,7 +90,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
     static final String DEF_SERVLET_API_PROVISION = "true";
 
     static final String ATT_ACCESS_MGR = "access-decision-manager-ref";    
-    static final String ATT_USER_SERVICE_REF = "user-service-ref";    
+    static final String ATT_USER_SERVICE_REF = "user-service-ref";
 
     public BeanDefinition parse(Element element, ParserContext parserContext) {
         BeanDefinitionRegistry registry = parserContext.getRegistry();

+ 7 - 0
core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc

@@ -211,6 +211,9 @@ http.attlist &=
 http.attlist &=
     ## Indicates whether an existing session should be invalidated when a user authenticates and a new session started. If set to "none" no change will be made. "newSession" will create a new empty session. "migrateSession" will create a new session and copy the session attributes to the new session. Defaults to "migrateSession".
     attribute session-fixation-protection {"none" | "newSession" | "migrateSession" }?
+http.attlist &=
+    ## Allows a customized AuthenticationEntryPoint to be used.
+    attribute entry-point-ref {xsd:string}?
 
 
 intercept-url =
@@ -307,6 +310,10 @@ concurrent-sessions.attlist &=
     attribute expired-url {xsd:string}?
 concurrent-sessions.attlist &=
     attribute exception-if-maximum-exceeded {"true" | "false"}?
+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}?
+
 
 remember-me =
     element remember-me {remember-me.attlist}

+ 71 - 31
core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd

@@ -2,6 +2,7 @@
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
     xmlns:security="http://www.springframework.org/schema/security" elementFormDefault="qualified"
     targetNamespace="http://www.springframework.org/schema/security">
+
     <xs:attributeGroup name="hash">
         <xs:attribute name="hash" use="required">
             <xs:annotation>
@@ -92,35 +93,7 @@
             </xs:annotation>
         </xs:attribute>
     </xs:attributeGroup>
-    <xs:element name="password-encoder">
-        <xs:annotation>
-            <xs:documentation>element which defines a password encoding strategy. Used by an
-                authentication provider to convert submitted passwords to hashed versions, for
-                example.</xs:documentation>
-        </xs:annotation>
-        <xs:complexType>
-            <xs:sequence>
-                <xs:element minOccurs="0" name="salt-source">
-                    <xs:complexType>
-                        <xs:attribute name="user-property" type="xs:string">
-                            <xs:annotation>
-                                <xs:documentation>A property of the UserDetails object which will be
-                                    used as salt by a password encoder. Typically something like
-                                    "username" might be used. </xs:documentation>
-                            </xs:annotation>
-                        </xs:attribute>
-                        <xs:attribute name="system-wide" type="xs:string">
-                            <xs:annotation>
-                                <xs:documentation>A single value that will be used as the salt for a
-                                    password encoder. </xs:documentation>
-                            </xs:annotation>
-                        </xs:attribute>
-                    </xs:complexType>
-                </xs:element>
-            </xs:sequence>
-            <xs:attributeGroup ref="security:password-encoder.attlist"/>
-        </xs:complexType>
-    </xs:element>
+
     <xs:attributeGroup name="password-encoder.attlist">
         <xs:attribute name="ref" type="xs:string">
             <xs:annotation>
@@ -365,7 +338,35 @@
         </xs:annotation>
         <xs:complexType>
             <xs:sequence>
-                <xs:element minOccurs="0" ref="security:password-encoder"/>
+                <xs:element minOccurs="0" name="password-encoder">
+                    <xs:annotation>
+                        <xs:documentation>element which defines a password encoding strategy. Used
+                            by an authentication provider to convert submitted passwords to hashed
+                            versions, for example.</xs:documentation>
+                    </xs:annotation>
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element minOccurs="0" name="salt-source">
+                                <xs:complexType>
+                                    <xs:attribute name="user-property" type="xs:string">
+                                        <xs:annotation>
+                                            <xs:documentation>A property of the UserDetails object
+                                                which will be used as salt by a password encoder.
+                                                Typically something like "username" might be used. </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                    <xs:attribute name="system-wide" type="xs:string">
+                                        <xs:annotation>
+                                            <xs:documentation>A single value that will be used as
+                                                the salt for a password encoder. </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                </xs:complexType>
+                            </xs:element>
+                        </xs:sequence>
+                        <xs:attributeGroup ref="security:password-encoder.attlist"/>
+                    </xs:complexType>
+                </xs:element>
             </xs:sequence>
             <xs:attributeGroup ref="security:password-compare.attlist"/>
         </xs:complexType>
@@ -706,6 +707,11 @@
                 </xs:restriction>
             </xs:simpleType>
         </xs:attribute>
+        <xs:attribute name="entry-point-ref" type="xs:string">
+            <xs:annotation>
+                <xs:documentation>Allows a customized AuthenticationEntryPoint to be used.</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
     </xs:attributeGroup>
 
     <xs:attributeGroup name="intercept-url.attlist">
@@ -935,6 +941,12 @@
                 </xs:restriction>
             </xs:simpleType>
         </xs:attribute>
+        <xs:attribute name="session-registry-alias" type="xs:string">
+            <xs:annotation>
+                <xs:documentation>Allows you to define an alias for the SessionRegistry bean in
+                    order to access it in your own configuration</xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
     </xs:attributeGroup>
 
     <xs:attributeGroup name="remember-me.attlist">
@@ -1019,7 +1031,35 @@
         <xs:complexType>
             <xs:choice minOccurs="0" maxOccurs="unbounded">
                 <xs:group ref="security:any-user-service"/>
-                <xs:element ref="security:password-encoder"/>
+                <xs:element name="password-encoder">
+                    <xs:annotation>
+                        <xs:documentation>element which defines a password encoding strategy. Used
+                            by an authentication provider to convert submitted passwords to hashed
+                            versions, for example.</xs:documentation>
+                    </xs:annotation>
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:element minOccurs="0" name="salt-source">
+                                <xs:complexType>
+                                    <xs:attribute name="user-property" type="xs:string">
+                                        <xs:annotation>
+                                            <xs:documentation>A property of the UserDetails object
+                                                which will be used as salt by a password encoder.
+                                                Typically something like "username" might be used. </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                    <xs:attribute name="system-wide" type="xs:string">
+                                        <xs:annotation>
+                                            <xs:documentation>A single value that will be used as
+                                                the salt for a password encoder. </xs:documentation>
+                                        </xs:annotation>
+                                    </xs:attribute>
+                                </xs:complexType>
+                            </xs:element>
+                        </xs:sequence>
+                        <xs:attributeGroup ref="security:password-encoder.attlist"/>
+                    </xs:complexType>
+                </xs:element>
             </xs:choice>
             <xs:attributeGroup ref="security:ap.attlist"/>
         </xs:complexType>

+ 1 - 1
core/src/main/resources/org/springframework/security/config/spring-security.xsl

@@ -10,7 +10,7 @@
     <xsl:output method="xml" indent="yes"/>
 
     <xsl:variable name="elts-to-inline">
-        <xsl:text>,intercept-url,form-login,x509,http-basic,logout,concurrent-session-control,remember-me,anonymous,port-mappings,password-compare-element,salt-source,filter-chain,protect-pointcut,</xsl:text>
+        <xsl:text>,intercept-url,form-login,x509,password-encoder,http-basic,logout,concurrent-session-control,remember-me,anonymous,port-mappings,password-compare-element,salt-source,filter-chain,protect-pointcut,</xsl:text>
     </xsl:variable>
 
     <xsl:template match="xs:element">

+ 55 - 1
core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java

@@ -1,5 +1,10 @@
 package org.springframework.security.config;
 
+import org.springframework.security.concurrent.ConcurrentLoginException;
+import org.springframework.security.concurrent.ConcurrentSessionController;
+import org.springframework.security.concurrent.ConcurrentSessionControllerImpl;
+import org.springframework.security.concurrent.ConcurrentSessionFilter;
+import org.springframework.security.concurrent.SessionRegistryImpl;
 import org.springframework.security.context.HttpSessionContextIntegrationFilter;
 import org.springframework.security.intercept.web.FilterSecurityInterceptor;
 import org.springframework.security.intercept.web.FilterInvocationDefinitionSource;
@@ -7,6 +12,7 @@ import org.springframework.security.intercept.web.FilterInvocation;
 import org.springframework.security.securechannel.ChannelProcessingFilter;
 import org.springframework.security.ui.ExceptionTranslationFilter;
 import org.springframework.security.ui.SessionFixationProtectionFilter;
+import org.springframework.security.ui.WebAuthenticationDetails;
 import org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter;
 import org.springframework.security.ui.basicauth.BasicProcessingFilter;
 import org.springframework.security.ui.logout.LogoutFilter;
@@ -18,13 +24,17 @@ import org.springframework.security.util.FilterChainProxy;
 import org.springframework.security.util.PortMapperImpl;
 import org.springframework.security.util.InMemoryXmlApplicationContext;
 import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter;
+import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
 import org.springframework.security.providers.anonymous.AnonymousProcessingFilter;
+import org.springframework.security.Authentication;
 import org.springframework.security.MockFilterChain;
 import org.springframework.security.ConfigAttributeDefinition;
 import org.springframework.security.SecurityConfig;
+import org.springframework.beans.BeanUtils;
 import org.springframework.context.support.AbstractXmlApplicationContext;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
 
 import static org.junit.Assert.*;
 import org.junit.Test;
@@ -247,6 +257,50 @@ public class HttpSecurityBeanDefinitionParserTests {
         assertTrue(filters.get(3) instanceof X509PreAuthenticatedProcessingFilter);
     }
 
+    @Test
+    public void concurrentSessionSupportAddsFilterAndExpectedBeans() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
+                "</http>"  + AUTH_PROVIDER_XML);
+        List filters = getFilterChainProxy().getFilters("/someurl");
+        
+        assertTrue(filters.get(0) instanceof ConcurrentSessionFilter);        
+        assertNotNull(appContext.getBean("seshRegistry"));
+        assertNotNull(appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER));
+    }    
+
+    @Test(expected=ConcurrentLoginException.class)
+    public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <concurrent-session-control max-sessions='2' exception-if-maximum-exceeded='true' />" +
+                "</http>"  + AUTH_PROVIDER_XML);
+        ConcurrentSessionControllerImpl seshController = (ConcurrentSessionControllerImpl) appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER);
+        UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
+        // Register 2 sessions and then check a third
+        MockHttpServletRequest req = new MockHttpServletRequest();
+        req.setSession(new MockHttpSession());
+        auth.setDetails(new WebAuthenticationDetails(req));
+        try {
+        	seshController.checkAuthenticationAllowed(auth);
+        } catch (ConcurrentLoginException e) {
+        	fail("First login should be allowed");
+        }        
+        seshController.registerSuccessfulAuthentication(auth);
+        req.setSession(new MockHttpSession());
+        try {
+        	seshController.checkAuthenticationAllowed(auth);
+        } catch (ConcurrentLoginException e) {
+        	fail("Second login should be allowed");
+        }
+        auth.setDetails(new WebAuthenticationDetails(req));
+        seshController.registerSuccessfulAuthentication(auth);
+        req.setSession(new MockHttpSession());
+        auth.setDetails(new WebAuthenticationDetails(req));
+        seshController.checkAuthenticationAllowed(auth);
+    }
+
     @Test
     public void disablingSessionProtectionRemovesFilter() throws Exception {
         setContext(
@@ -254,7 +308,7 @@ public class HttpSecurityBeanDefinitionParserTests {
         List filters = getFilterChainProxy().getFilters("/someurl");
 
         assertFalse(filters.get(1) instanceof SessionFixationProtectionFilter);
-    }
+    }    
     
     private void setContext(String context) {
         appContext = new InMemoryXmlApplicationContext(context);