From fbe3ca48f4031aa5b6b97546a21bcf1d1b8deaa0 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Wed, 21 May 2008 16:03:05 +0000 Subject: [PATCH] SEC-823, SEC-843: Allow setting of custom RememberMeServices and token validity periodon remember-me namespace element --- .../RememberMeBeanDefinitionParser.java | 108 +++++++++++------- .../security/config/spring-security-2.0.2.rnc | 12 +- .../security/config/spring-security-2.0.2.xsd | 16 ++- ...HttpSecurityBeanDefinitionParserTests.java | 44 +++++-- 4 files changed, 125 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java index 624c6b2286..a590da5314 100644 --- a/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java +++ b/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); } - - BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext); - 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); - - 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)); + + registerProvider(parserContext, source, key); + + registerFilter(parserContext, source); return null; } + + private void registerProvider(ParserContext pc, Object source, String key) { + BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(pc); + RootBeanDefinition provider = new RootBeanDefinition(RememberMeAuthenticationProvider.class); + provider.setSource(source); + provider.getPropertyValues().addPropertyValue(ATT_KEY, key); + ManagedList providers = (ManagedList) authManager.getPropertyValues().getPropertyValue("providers").getValue(); + 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)); + + pc.getRegistry().registerBeanDefinition(BeanIds.REMEMBER_ME_FILTER, filter); + ConfigUtils.addHttpFilter(pc, new RuntimeBeanReference(BeanIds.REMEMBER_ME_FILTER)); + } } diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc index 8a2c46a255..d588171089 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.rnc +++ b/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 diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd index 2be47bb661..4d85045fd0 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.2.xsd @@ -1024,7 +1024,13 @@ - + + + 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. + + Reference to a PersistentTokenRepository bean for use with the persistent @@ -1043,7 +1049,7 @@ Id - + The period (in seconds) for which the remember-me cookie should be valid. @@ -1061,8 +1067,10 @@ - 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. diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java index c92627e4b5..8c62371a60 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/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 = " " + - " " + + " " + " " + " " + " " + @@ -340,23 +342,50 @@ public class HttpSecurityBeanDefinitionParserTests { public void rememberMeServiceWorksWithTokenRepoRef() { setContext( "" + - " " + + " " + "" + " " + AUTH_PROVIDER_XML); Object rememberMeServices = appContext.getBean(BeanIds.REMEMBER_ME_SERVICES); - + assertTrue(rememberMeServices instanceof PersistentTokenBasedRememberMeServices); } + @Test + public void rememberMeServiceWorksWithExternalServicesImpl() throws Exception { + setContext( + "" + + " " + + "" + + " " + + " " + + " " + + " " + + "" + + AUTH_PROVIDER_XML); + + assertEquals(5000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES), + "tokenValiditySeconds")); + } + + @Test + public void rememberMeTokenValidityIsParsedCorrectly() throws Exception { + setContext( + "" + + " " + + "" + AUTH_PROVIDER_XML); + assertEquals(10000, FieldUtils.getFieldValue(appContext.getBean(BeanIds.REMEMBER_ME_SERVICES), + "tokenValiditySeconds")); + } + @Test public void rememberMeServiceConfigurationParsesWithCustomUserService() { setContext( "" + - " " + + " " + "" + - " " + AUTH_PROVIDER_XML); + " " + + AUTH_PROVIDER_XML); // AbstractRememberMeServices rememberMeServices = (AbstractRememberMeServices) appContext.getBean(BeanIds.REMEMBER_ME_SERVICES); } @@ -509,8 +538,7 @@ public class HttpSecurityBeanDefinitionParserTests { " " + " " + AUTH_PROVIDER_XML); } - - + private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context); }