Explorar o código

SEC-821: Added support for eternal session registry and concurrent session controller to the 2.0.2 namespace.

Luke Taylor %!s(int64=17) %!d(string=hai) anos
pai
achega
d63536cc0d

+ 16 - 3
core/src/main/java/org/springframework/security/config/AuthenticationManagerBeanDefinitionParser.java

@@ -3,27 +3,40 @@ package org.springframework.security.config;
 import org.springframework.beans.factory.xml.BeanDefinitionParser;
 import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.RuntimeBeanReference;
 import org.springframework.util.StringUtils;
 
 import org.w3c.dom.Element;
 
 /**
- * Just registers an alias name for the default ProviderManager used by the namespace
+ * Registers an alias name for the default ProviderManager used by the namespace
  * configuration, allowing users to reference it in their beans and clearly see where the name is
- * coming from.
+ * coming from. Also allows the ConcurrentSessionController to be set on the ProviderManager.
  *
  * @author Luke Taylor
  * @version $Id$
  */
 public class AuthenticationManagerBeanDefinitionParser implements BeanDefinitionParser {
-    private static final String ATT_ALIAS = "alias";
+    private static final String ATT_SESSION_CONTROLLER_REF = "session-controller-ref";
+	private static final String ATT_ALIAS = "alias";
 
     public BeanDefinition parse(Element element, ParserContext parserContext) {
+    	BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
+    	
         String alias = element.getAttribute(ATT_ALIAS);
 
         if (!StringUtils.hasText(alias)) {
             parserContext.getReaderContext().error(ATT_ALIAS + " is required.", element );
         }
+        
+        String sessionControllerRef = element.getAttribute(ATT_SESSION_CONTROLLER_REF);
+        
+        if (StringUtils.hasText(sessionControllerRef)) {
+            ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext, 
+            		BeanIds.CONCURRENT_SESSION_CONTROLLER, element);
+        	authManager.getPropertyValues().addPropertyValue("sessionController", 
+        			new RuntimeBeanReference(sessionControllerRef));
+        }
 
         parserContext.getRegistry().registerAlias(BeanIds.AUTHENTICATION_MANAGER, alias);
 

+ 29 - 20
core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java

@@ -29,7 +29,8 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
     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";    
+    static final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias";
+    static final String ATT_SESSION_REGISTRY_REF = "session-registry-ref";
 	
     public BeanDefinition parse(Element element, ParserContext parserContext) {
     	CompositeComponentDefinition compositeDef =
@@ -38,26 +39,43 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
     	
     	BeanDefinitionRegistry beanRegistry = parserContext.getRegistry();
 
-        RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);
+        String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF);
+        
+        if (!StringUtils.hasText(sessionRegistryId)) {        	
+            RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);        
+            beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry);
+            parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY));
+            sessionRegistryId = BeanIds.SESSION_REGISTRY;
+        } else {
+        	// Register the default ID as an alias so that things like session fixation filter can access it
+        	beanRegistry.registerAlias(sessionRegistryId, BeanIds.SESSION_REGISTRY);
+        }
+
+        String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS);
+        if (StringUtils.hasText(registryAlias)) {
+        	beanRegistry.registerAlias(sessionRegistryId, registryAlias);
+        }    	
+
         BeanDefinitionBuilder filterBuilder =
                 BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class);
-        BeanDefinitionBuilder controllerBuilder
-                = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class);
-        controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY));
-        filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY));
+        filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId));
 
         Object source = parserContext.extractSource(element);
         filterBuilder.setSource(source);
         filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
-        controllerBuilder.setSource(source);
-        controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
-        
+
         String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
 
         if (StringUtils.hasText(expiryUrl)) {
         	ConfigUtils.validateHttpRedirect(expiryUrl, parserContext, source);
             filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
-        }
+        }        
+
+        BeanDefinitionBuilder controllerBuilder
+        	= BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class);
+        controllerBuilder.setSource(source);
+        controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);        
+        controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId));
 
         String maxSessions = element.getAttribute(ATT_MAX_SESSIONS);
 
@@ -72,23 +90,14 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
         }
 
         BeanDefinition controller = controllerBuilder.getBeanDefinition();
-        beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry);
-        parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY));
         
-        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);
         parserContext.registerComponent(new BeanComponentDefinition(controller, BeanIds.CONCURRENT_SESSION_CONTROLLER));
         beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_FILTER, filterBuilder.getBeanDefinition());
         parserContext.registerComponent(new BeanComponentDefinition(filterBuilder.getBeanDefinition(), BeanIds.CONCURRENT_SESSION_FILTER));
         ConfigUtils.addHttpFilter(parserContext, new RuntimeBeanReference(BeanIds.CONCURRENT_SESSION_FILTER));
         
-        BeanDefinition providerManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
-
-        providerManager.getPropertyValues().addPropertyValue("sessionController", controller);
+        ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext, BeanIds.CONCURRENT_SESSION_CONTROLLER, element);
         
         parserContext.popAndRegisterContainingComponent();
         

+ 15 - 0
core/src/main/java/org/springframework/security/config/ConfigUtils.java

@@ -7,6 +7,7 @@ import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.springframework.beans.BeanMetadataElement;
 import org.springframework.beans.MutablePropertyValues;
+import org.springframework.beans.PropertyValue;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -22,6 +23,7 @@ import org.springframework.security.vote.AffirmativeBased;
 import org.springframework.security.vote.AuthenticatedVoter;
 import org.springframework.security.vote.RoleVoter;
 import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
 
 /**
  * Utility methods used internally by the Spring Security namespace configuration code.
@@ -168,4 +170,17 @@ public abstract class ConfigUtils {
     	}
     	pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source);
     }
+    
+    static void setSessionControllerOnAuthenticationManager(ParserContext pc, String beanName, Element sourceElt) {
+    	BeanDefinition authManager = registerProviderManagerIfNecessary(pc);
+        PropertyValue pv = authManager.getPropertyValues().getPropertyValue("sessionController");
+        
+        if (pv != null && pv.getValue() != null) {
+        	pc.getReaderContext().error("A session controller has already been set on the authentication manager. " +
+        			"The <concurrent-session-control> element isn't compatible with a custom session controller", 
+        			pc.extractSource(sourceElt));
+        }
+        
+        authManager.getPropertyValues().addPropertyValue("sessionController", new RuntimeBeanReference(beanName));
+    }
 }

+ 11 - 3
core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc

@@ -335,13 +335,17 @@ concurrent-session-control =
 concurrent-sessions.attlist &=
     attribute max-sessions {xsd:positiveInteger}?
 concurrent-sessions.attlist &=
+    ## The URL a user will be redirected to if they attempt to use a session which has been "expired" by the concurrent session controller.
     attribute expired-url {xsd:string}?
 concurrent-sessions.attlist &=
+    ## Specifies that an exception should be raised when a user attempts to login twice. The default behaviour is to expire the original session.
     attribute exception-if-maximum-exceeded {boolean}?
 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}?
-
+concurrent-sessions.attlist &=
+    ## A reference to an external SessionRegistry implementation which will be used in place of the standard one. 
+    attribute session-registry-ref {xsd:string}?
 
 remember-me =
     ## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach.     
@@ -408,11 +412,15 @@ x509.attlist &=
     user-service-ref?
 
 authentication-manager =
-    ## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element simple allows you to define an alias to allow you to reference the authentication-manager in your own beans. 
+    ## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element allows you to define an alias to allow you to reference the authentication-manager in your own beans. 
     element authentication-manager {authman.attlist}
-    ## The alias you wish to use for the AuthenticationManager bean
 authman.attlist &=
+    ## The alias you wish to use for the AuthenticationManager bean
     attribute alias {xsd:ID}
+authman.attlist &=
+    ## Allows the session controller to be set on the internal AuthenticationManager. This should not be used with the <concurrent-session-control /> element
+    attribute session-controller-ref {xsd:string}?
+
 
 authentication-provider =
     ## Indicates that the contained user-service should be used as an authentication source. 

+ 43 - 20
core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd

@@ -489,7 +489,16 @@
     </xs:annotation>
     <xs:complexType>
       <xs:sequence>
-        <xs:element maxOccurs="unbounded" ref="security:protect"/>
+        <xs:element maxOccurs="unbounded" name="protect">
+          <xs:annotation>
+            <xs:documentation>Defines a protected method and the access control configuration
+              attributes that apply to it. We strongly advise you NOT to mix "protect" declarations
+              with any services provided "global-method-security".</xs:documentation>
+          </xs:annotation>
+          <xs:complexType>
+            <xs:attributeGroup ref="security:protect.attlist"/>
+          </xs:complexType>
+        </xs:element>
       </xs:sequence>
       <xs:attributeGroup ref="security:intercept-methods.attlist"/>
     </xs:complexType>
@@ -502,16 +511,6 @@
       </xs:annotation>
     </xs:attribute>
   </xs:attributeGroup>
-  <xs:element name="protect">
-    <xs:annotation>
-      <xs:documentation>Defines a protected method and the access control configuration attributes
-        that apply to it. We strongly advise you NOT to mix "protect" declarations with any services
-        provided "global-method-security".</xs:documentation>
-    </xs:annotation>
-    <xs:complexType>
-      <xs:attributeGroup ref="security:protect.attlist"/>
-    </xs:complexType>
-  </xs:element>
   <xs:attributeGroup name="protect.attlist">
     <xs:attribute name="method" use="required" type="xs:string">
       <xs:annotation>
@@ -1014,14 +1013,30 @@
   </xs:attributeGroup>
   <xs:attributeGroup name="concurrent-sessions.attlist">
     <xs:attribute name="max-sessions" type="xs:positiveInteger"/>
-    <xs:attribute name="expired-url" type="xs:string"/>
-    <xs:attribute name="exception-if-maximum-exceeded" type="security:boolean"/>
+    <xs:attribute name="expired-url" type="xs:string">
+      <xs:annotation>
+        <xs:documentation>The URL a user will be redirected to if they attempt to use a session
+          which has been "expired" by the concurrent session controller.</xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="exception-if-maximum-exceeded" type="security:boolean">
+      <xs:annotation>
+        <xs:documentation>Specifies that an exception should be raised when a user attempts to login
+          twice. The default behaviour is to expire the original session.</xs:documentation>
+      </xs:annotation>
+    </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:attribute name="session-registry-ref" type="xs:string">
+      <xs:annotation>
+        <xs:documentation>A reference to an external SessionRegistry implementation which will be
+          used in place of the standard one. </xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
   </xs:attributeGroup>
   <xs:attributeGroup name="remember-me.attlist">
     <xs:attribute name="key" type="xs:string">
@@ -1130,8 +1145,8 @@
   <xs:element name="authentication-manager">
     <xs:annotation>
       <xs:documentation>If you are using namespace configuration with Spring Security, an
-        AuthenticationManager will automatically be registered. This element simple allows you to
-        define an alias to allow you to reference the authentication-manager in your own beans.
+        AuthenticationManager will automatically be registered. This element allows you to define an
+        alias to allow you to reference the authentication-manager in your own beans.
       </xs:documentation>
     </xs:annotation>
     <xs:complexType>
@@ -1139,11 +1154,19 @@
     </xs:complexType>
   </xs:element>
   <xs:attributeGroup name="authman.attlist">
-    <xs:annotation>
-      <xs:documentation>The alias you wish to use for the AuthenticationManager
-      bean</xs:documentation>
-    </xs:annotation>
-    <xs:attribute name="alias" use="required" type="xs:ID"/>
+    <xs:attribute name="alias" use="required" type="xs:ID">
+      <xs:annotation>
+        <xs:documentation>The alias you wish to use for the AuthenticationManager
+        bean</xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
+    <xs:attribute name="session-controller-ref" type="xs:string">
+      <xs:annotation>
+        <xs:documentation>Allows the session controller to be set on the internal
+          AuthenticationManager. This should not be used with the &lt;concurrent-session-control
+          /&gt; element</xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
   </xs:attributeGroup>
   <xs:element name="authentication-provider">
     <xs:annotation>

+ 50 - 3
core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java

@@ -8,7 +8,6 @@ import java.util.List;
 
 import org.junit.After;
 import org.junit.Test;
-import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.BeanCreationException;
 import org.springframework.beans.factory.BeanDefinitionStoreException;
 import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -40,7 +39,6 @@ import org.springframework.security.ui.rememberme.NullRememberMeServices;
 import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices;
 import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
 import org.springframework.security.ui.rememberme.RememberMeServices;
-import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
 import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
 import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
 import org.springframework.security.util.FieldUtils;
@@ -405,7 +403,7 @@ public class HttpSecurityBeanDefinitionParserTests {
         setContext(
                 "<http auto-config='true'>" +
                 "    <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
-                "</http>"  + AUTH_PROVIDER_XML);
+                "</http>" + AUTH_PROVIDER_XML);
         List filters = getFilters("/someurl");
         
         assertTrue(filters.get(0) instanceof ConcurrentSessionFilter);        
@@ -413,6 +411,55 @@ public class HttpSecurityBeanDefinitionParserTests {
         assertNotNull(appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER));
     }    
 
+    @Test
+    public void externalSessionRegistryBeanIsConfiguredCorrectly() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <concurrent-session-control session-registry-ref='seshRegistry' />" +
+                "</http>" +
+                "<b:bean id='seshRegistry' class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
+                AUTH_PROVIDER_XML);
+        Object sessionRegistry = appContext.getBean("seshRegistry");
+        Object sessionRegistryFromFilter = FieldUtils.getFieldValue(
+        		appContext.getBean(BeanIds.CONCURRENT_SESSION_FILTER),"sessionRegistry");
+        Object sessionRegistryFromController = FieldUtils.getFieldValue(
+        		appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER),"sessionRegistry");
+        Object sessionRegistryFromFixationFilter = FieldUtils.getFieldValue(
+        		appContext.getBean(BeanIds.SESSION_FIXATION_PROTECTION_FILTER),"sessionRegistry");
+        
+        assertSame(sessionRegistry, sessionRegistryFromFilter);
+        assertSame(sessionRegistry, sessionRegistryFromController);
+        assertSame(sessionRegistry, sessionRegistryFromFixationFilter);
+    }
+
+    @Test(expected=BeanDefinitionParsingException.class)
+    public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean() throws Exception {
+        setContext(
+                "<authentication-manager alias='authManager' session-controller-ref='sc'/>" +
+                "<http auto-config='true'>" +
+                "    <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
+                "</http>" +
+                "<b:bean id='sc' class='org.springframework.security.concurrent.ConcurrentSessionControllerImpl'>" +
+                "  <b:property name='sessionRegistry'>" +
+                "    <b:bean class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
+                "  </b:property>" +
+                "</b:bean>" + AUTH_PROVIDER_XML);
+    }
+
+    @Test(expected=BeanDefinitionParsingException.class)
+    public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean2() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
+                "</http>" +
+                "<b:bean id='sc' class='org.springframework.security.concurrent.ConcurrentSessionControllerImpl'>" +
+                "  <b:property name='sessionRegistry'>" +
+                "    <b:bean class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
+                "  </b:property>" +
+                "</b:bean>" +
+                "<authentication-manager alias='authManager' session-controller-ref='sc'/>" + AUTH_PROVIDER_XML);
+    }    
+    
     @Test(expected=ConcurrentLoginException.class)
     public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
         setContext(