2
0
Эх сурвалжийг харах

SEC-823, SEC-843: Allow setting of custom RememberMeServices and token validity periodon remember-me namespace element

Luke Taylor 17 жил өмнө
parent
commit
fbe3ca48f4

+ 62 - 34
core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java

@@ -23,12 +23,14 @@ import org.w3c.dom.Element;
  */
 public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
     static final String ATT_KEY = "key";
-    static final String DEF_KEY = "doesNotMatter";
+    static final String DEF_KEY = "SpringSecured";
 
-	static final String ATT_DATA_SOURCE = "data-source";
+	static final String ATT_DATA_SOURCE = "data-source-ref";
+	static final String ATT_SERVICES_REF = "services-ref";
 	static final String ATT_TOKEN_REPOSITORY = "token-repository-ref";
 	static final String ATT_USER_SERVICE_REF = "user-service-ref";
-	
+	static final String ATT_TOKEN_VALIDITY = "token-validity-seconds";
+
 	protected final Log logger = LogFactory.getLog(getClass());
 
     public BeanDefinition parse(Element element, ParserContext parserContext) {
@@ -37,32 +39,46 @@ public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
         String key = null;
         Object source = null;
         String userServiceRef = null;
+        String rememberMeServicesRef = null;
+        String tokenValiditySeconds = null;
 
         if (element != null) {
             tokenRepository = element.getAttribute(ATT_TOKEN_REPOSITORY);
             dataSource = element.getAttribute(ATT_DATA_SOURCE);
             key = element.getAttribute(ATT_KEY);
-            userServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);            
+            userServiceRef = element.getAttribute(ATT_USER_SERVICE_REF);
+            rememberMeServicesRef = element.getAttribute(ATT_SERVICES_REF);
+            tokenValiditySeconds = element.getAttribute(ATT_TOKEN_VALIDITY);
             source = parserContext.extractSource(element);
         }
-
-        RootBeanDefinition filter = new RootBeanDefinition(RememberMeProcessingFilter.class);
-        RootBeanDefinition services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class);
-
-        filter.getPropertyValues().addPropertyValue("authenticationManager",
-                new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));
+        
+        if (!StringUtils.hasText(key)) {
+        	key = DEF_KEY;
+        }        
+        
+        RootBeanDefinition services = null;
 
         boolean dataSourceSet = StringUtils.hasText(dataSource);
         boolean tokenRepoSet = StringUtils.hasText(tokenRepository);
-
+        boolean servicesRefSet = StringUtils.hasText(rememberMeServicesRef);
+        boolean userServiceSet = StringUtils.hasText(userServiceRef);
+        boolean tokenValiditySet = StringUtils.hasText(tokenValiditySeconds);
+        
+        if (servicesRefSet && (dataSourceSet || tokenRepoSet || userServiceSet || tokenValiditySet)) {
+    		parserContext.getReaderContext().error(ATT_SERVICES_REF + " can't be used in combination with attributes " 
+    				+ ATT_TOKEN_REPOSITORY + "," + ATT_DATA_SOURCE + ", " + ATT_USER_SERVICE_REF + " or " + ATT_TOKEN_VALIDITY, source);        	
+        }
+        
         if (dataSourceSet && tokenRepoSet) {
-            parserContext.getReaderContext().error("Specify tokenRepository or dataSource but not both", element);
+            parserContext.getReaderContext().error("Specify " + ATT_TOKEN_REPOSITORY + " or " + 
+            		ATT_DATA_SOURCE +" but not both", source);
         }
 
         boolean isPersistent = dataSourceSet | tokenRepoSet;
 
         if (isPersistent) {
             Object tokenRepo;
+            services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class);
 
             if (tokenRepoSet) {
                 tokenRepo = new RuntimeBeanReference(tokenRepository);
@@ -72,39 +88,51 @@ public class RememberMeBeanDefinitionParser implements BeanDefinitionParser {
                         new RuntimeBeanReference(dataSource));
             }
             services.getPropertyValues().addPropertyValue("tokenRepository", tokenRepo);
-        } else {
-            isPersistent = false;
+        } else if (!servicesRefSet) {
             services = new RootBeanDefinition(TokenBasedRememberMeServices.class);
         }
 
-        if (!StringUtils.hasText(key) && !isPersistent) {
-        	key = DEF_KEY;
+        if (services != null) {
+	        if (userServiceSet) {
+	            services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
+	        }
+	        
+	        if (tokenValiditySet) {
+	        	services.getPropertyValues().addPropertyValue("tokenValiditySeconds", Integer.parseInt(tokenValiditySeconds));
+	        }
+	        services.setSource(source);
+	        services.getPropertyValues().addPropertyValue(ATT_KEY, key);
+	        parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_SERVICES, services);	        
+        } else {
+        	parserContext.getRegistry().registerAlias(rememberMeServicesRef, BeanIds.REMEMBER_ME_SERVICES);
         }
+        
+        registerProvider(parserContext, source, key);        
+        
+        registerFilter(parserContext, source);
 
-        BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
+        return null;
+    }
+    
+    private void registerProvider(ParserContext pc, Object source, String key) {
+        BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(pc);
         RootBeanDefinition provider = new RootBeanDefinition(RememberMeAuthenticationProvider.class);
-
-        filter.setSource(source);
-        services.setSource(source);
         provider.setSource(source);
-
-        if (StringUtils.hasText(userServiceRef)) {
-            services.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(userServiceRef));
-        }
-
         provider.getPropertyValues().addPropertyValue(ATT_KEY, key);
-        services.getPropertyValues().addPropertyValue(ATT_KEY, key);
-
         ManagedList providers = (ManagedList) authManager.getPropertyValues().getPropertyValue("providers").getValue();
-        providers.add(provider);
-
+        providers.add(provider);    	
+    }
+    
+    private void registerFilter(ParserContext pc, Object source) {
+        RootBeanDefinition filter = new RootBeanDefinition(RememberMeProcessingFilter.class);
+        filter.setSource(source);
+        filter.getPropertyValues().addPropertyValue("authenticationManager",
+                new RuntimeBeanReference(BeanIds.AUTHENTICATION_MANAGER));        
+        
         filter.getPropertyValues().addPropertyValue("rememberMeServices",
                 new RuntimeBeanReference(BeanIds.REMEMBER_ME_SERVICES));
 
-        parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_SERVICES, services);
-        parserContext.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_FILTER, filter);
-        ConfigUtils.addHttpFilter(parserContext, new RuntimeBeanReference(BeanIds.REMEMBER_ME_FILTER));
-
-        return null;
+        pc.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_FILTER, filter);
+        ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.REMEMBER_ME_FILTER));    	
     }
 }

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

@@ -347,18 +347,24 @@ 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.     
     element remember-me {remember-me.attlist}
 remember-me.attlist &=
-    (attribute key {xsd:string} | token-repository-ref | remember-me-data-source-ref | remember-me-services-ref)
+    ## The "key" used to identify cookies from a specific token-based remember-me application. You should set this to a unique value for your application.
+    attribute key {xsd:string}?
+    
+remember-me.attlist &=
+    (token-repository-ref | remember-me-data-source-ref | remember-me-services-ref)
+
 remember-me.attlist &=
     user-service-ref?
+    
 remember-me.attlist &=
     ## The period (in seconds) for which the remember-me cookie should be valid. 
-    attribute token-validity-period {xsd:positiveInteger}?
+    attribute token-validity-seconds {xsd:positiveInteger}?
     
 token-repository-ref =
     ## Reference to a PersistentTokenRepository bean for use with the persistent token remember-me implementation. 
     attribute token-repository-ref {xsd:string}
 remember-me-services-ref =     
-    ## Allows a custom implementation of RememberMeServices to be used.
+    ## Allows a custom implementation of RememberMeServices to be used. Note that this implementation should return RememberMeAuthenticationToken instances with the same "key" value as specified in the remember-me element. Alternatively it should register its own AuthenticationProvider. 
     attribute services-ref {xsd:string}?
 remember-me-data-source-ref =
     ## DataSource bean for the database that contains the token  

+ 12 - 4
core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd

@@ -1024,7 +1024,13 @@
     </xs:attribute>
   </xs:attributeGroup>
   <xs:attributeGroup name="remember-me.attlist">
-    <xs:attribute name="key" type="xs:string"/>
+    <xs:attribute name="key" type="xs:string">
+      <xs:annotation>
+        <xs:documentation>The "key" used to identify cookies from a specific token-based remember-me
+          application. You should set this to a unique value for your
+        application.</xs:documentation>
+      </xs:annotation>
+    </xs:attribute>
     <xs:attribute name="token-repository-ref" type="xs:string">
       <xs:annotation>
         <xs:documentation>Reference to a PersistentTokenRepository bean for use with the persistent
@@ -1043,7 +1049,7 @@
         Id</xs:documentation>
       </xs:annotation>
     </xs:attribute>
-    <xs:attribute name="token-validity-period" type="xs:positiveInteger">
+    <xs:attribute name="token-validity-seconds" type="xs:positiveInteger">
       <xs:annotation>
         <xs:documentation>The period (in seconds) for which the remember-me cookie should be valid.
         </xs:documentation>
@@ -1061,8 +1067,10 @@
   <xs:attributeGroup name="remember-me-services-ref">
     <xs:attribute name="services-ref" type="xs:string">
       <xs:annotation>
-        <xs:documentation>Allows a custom implementation of RememberMeServices to be
-        used.</xs:documentation>
+        <xs:documentation>Allows a custom implementation of RememberMeServices to be used. Note that
+          this implementation should return RememberMeAuthenticationToken instances with the same
+          "key" value as specified in the remember-me element. Alternatively it should register its
+          own AuthenticationProvider. </xs:documentation>
       </xs:annotation>
     </xs:attribute>
   </xs:attributeGroup>

+ 36 - 8
core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java

@@ -8,6 +8,7 @@ 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;
@@ -39,6 +40,7 @@ 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;
@@ -57,7 +59,7 @@ public class HttpSecurityBeanDefinitionParserTests {
     private AbstractXmlApplicationContext appContext;
     static final String AUTH_PROVIDER_XML =
             "    <authentication-provider>" +
-            "        <user-service>" +
+            "        <user-service id='us'>" +
             "            <user name='bob' password='bobspassword' authorities='ROLE_A,ROLE_B' />" +
             "            <user name='bill' password='billspassword' authorities='ROLE_A,ROLE_B,AUTH_OTHER' />" +
             "        </user-service>" +
@@ -340,23 +342,50 @@ public class HttpSecurityBeanDefinitionParserTests {
     public void rememberMeServiceWorksWithTokenRepoRef() {
         setContext(
                 "<http auto-config='true'>" +
-                "    <remember-me key='doesntmatter' token-repository-ref='tokenRepo'/>" +
+                "    <remember-me token-repository-ref='tokenRepo'/>" +
                 "</http>" +
                 "<b:bean id='tokenRepo' " +
                         "class='org.springframework.security.ui.rememberme.InMemoryTokenRepositoryImpl'/> " + AUTH_PROVIDER_XML);
         Object rememberMeServices = appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
-
+        
         assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices);
     }
 
+    @Test
+    public void rememberMeServiceWorksWithExternalServicesImpl() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <remember-me key='ourkey' services-ref='rms'/>" +
+                "</http>" +
+                "<b:bean id='rms' class='org.springframework.security.ui.rememberme.TokenBasedRememberMeServices'> " +
+                "    <b:property name='userDetailsService' ref='us'/>" +
+                "    <b:property name='key' value='ourkey'/>" +
+                "    <b:property name='tokenValiditySeconds' value='5000'/>" +
+                "</b:bean>" +
+                AUTH_PROVIDER_XML);
+        
+        assertEquals(5000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES), 
+        		"tokenValiditySeconds"));        
+    }
+
+    @Test
+    public void rememberMeTokenValidityIsParsedCorrectly() throws Exception {
+        setContext(
+                "<http auto-config='true'>" +
+                "    <remember-me key='ourkey' token-validity-seconds='10000' />" +
+                "</http>" + AUTH_PROVIDER_XML);
+        assertEquals(10000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES), 
+				"tokenValiditySeconds"));
+    }    
+    
     @Test
     public void rememberMeServiceConfigurationParsesWithCustomUserService() {
         setContext(
                 "<http auto-config='true'>" +
-                "    <remember-me key='doesntmatter' user-service-ref='userService'/>" +
+                "    <remember-me key='somekey' user-service-ref='userService'/>" +
                 "</http>" +
-                "<b:bean id='userService' " +
-                        "class='org.springframework.security.userdetails.MockUserDetailsService'/> " + AUTH_PROVIDER_XML);
+                "<b:bean id='userService' class='org.springframework.security.userdetails.MockUserDetailsService'/> " +
+                AUTH_PROVIDER_XML);
 //        AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES);
     }    
     
@@ -509,8 +538,7 @@ public class HttpSecurityBeanDefinitionParserTests {
                 "        <form-login login-page='/login.jsp' default-target-url='/messageList.html'/>" +
                 "    </http>" + AUTH_PROVIDER_XML);
     }
-    
-    
+
     private void setContext(String context) {
         appContext = new InMemoryXmlApplicationContext(context);
     }