From 9f2bc9a84223d2f03f02b2cd3341a76f78c73851 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Tue, 6 Nov 2007 23:20:25 +0000 Subject: [PATCH] SEC-582: Namespace configuration implementation for remember-me support. --- .../security/config/ConfigUtils.java | 17 ++++- .../config/FormLoginBeanDefinitionParser.java | 9 ++- .../HttpSecurityBeanDefinitionParser.java | 8 ++- .../HttpSecurityConfigPostProcessor.java | 59 ++++++++++++++- .../RememberMeBeanDefinitionParser.java | 72 +++++++++++++++++++ .../AbstractRememberMeServices.java | 36 ++++++++-- .../rememberme/JdbcTokenRepositoryImpl.java | 34 +++++++++ ...ersistentTokenBasedRememberMeServices.java | 8 ++- .../DefaultLoginPageGeneratingFilter.java | 5 +- .../security/config/spring-security-2.0.rnc | 10 ++- .../security/config/spring-security-2.0.xsd | 25 +++++-- ...HttpSecurityBeanDefinitionParserTests.java | 13 ++-- .../security/config/http-security.xml | 13 +++- .../applicationContext-security-ns.xml | 2 + 14 files changed, 275 insertions(+), 36 deletions(-) create mode 100644 core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java create mode 100644 core/src/main/java/org/springframework/security/ui/rememberme/JdbcTokenRepositoryImpl.java diff --git a/core/src/main/java/org/springframework/security/config/ConfigUtils.java b/core/src/main/java/org/springframework/security/config/ConfigUtils.java index 2e156b431d..ec0edd8864 100644 --- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java +++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java @@ -4,12 +4,13 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 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.support.ManagedList; +import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.AccessDecisionManager; import org.springframework.security.AuthenticationManager; import org.springframework.security.providers.ProviderManager; +import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.vote.AffirmativeBased; import org.springframework.security.vote.AuthenticatedVoter; import org.springframework.security.vote.RoleVoter; @@ -93,6 +94,20 @@ public abstract class ConfigUtils { getAuthenticationManager(beanFactory)); } + static UserDetailsService getUserDetailsService(ConfigurableListableBeanFactory bf) { + Map services = bf.getBeansOfType(UserDetailsService.class); + + if (services.size() == 0) { + throw new IllegalArgumentException("No UserDetailsService registered."); + + } else if (services.size() > 1) { + throw new IllegalArgumentException("More than one UserDetailsService registered. Please" + + "use a specific Id in yur configuration"); + } + + return (UserDetailsService) services.values().toArray()[0]; + } + private static AuthenticationManager getAuthenticationManager(ConfigurableListableBeanFactory bf) { Map authManagers = bf.getBeansOfType(AuthenticationManager.class); diff --git a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java index fffadae86e..f0967d68b5 100644 --- a/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java @@ -29,7 +29,7 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { private static final String LOGIN_PAGE_ATTRIBUTE = "loginPage"; private static final String FORM_LOGIN_TARGET_URL_ATTRIBUTE = "defaultTargetUrl"; - private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/index"; + private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/"; private static final String FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE = "defaultTargetUrl"; // TODO: Change AbstractProcessingFilter to not need a failure URL and just write a failure message @@ -38,8 +38,13 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { public BeanDefinition parse(Element elt, ParserContext parserContext) { + ConfigUtils.registerProviderManagerIfNecessary(parserContext); + BeanDefinition filterBean = createFilterBean(elt); + filterBean.getPropertyValues().addPropertyValue("authenticationManager", + new RuntimeBeanReference(ConfigUtils.DEFAULT_AUTH_MANAGER_ID)); + BeanDefinitionBuilder entryPointBuilder = BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class); @@ -90,8 +95,6 @@ public class FormLoginBeanDefinitionParser implements BeanDefinitionParser { } filterBuilder.addPropertyValue("authenticationFailureUrl", authenticationFailureUrl); - // Set autowire to pick up the authentication manager. - filterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); return filterBuilder.getBeanDefinition(); } diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index c4f3952ce4..e5c41496cd 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -21,7 +21,6 @@ import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; -import javax.servlet.Filter; import java.util.*; /** @@ -44,6 +43,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { 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 REMEMBER_ME_ELEMENT = "remember-me"; static final String PATH_PATTERN_ATTRIBUTE = "pattern"; static final String PATTERN_TYPE_ATTRIBUTE = "pathType"; @@ -120,6 +120,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext); } + Element rememberMeElt = DomUtils.getChildElementByTagName(element, REMEMBER_ME_ELEMENT); + + if (rememberMeElt != null) { + new RememberMeBeanDefinitionParser().parse(rememberMeElt, parserContext); + } + registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy); registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF); registry.registerBeanDefinition(DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID, diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java index 42eca561c2..b2452027a8 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java @@ -3,14 +3,17 @@ package org.springframework.security.config; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; -import org.springframework.security.AuthenticationManager; +import org.springframework.security.concurrent.ConcurrentSessionFilter; import org.springframework.security.context.HttpSessionContextIntegrationFilter; +import org.springframework.security.ui.AbstractProcessingFilter; import org.springframework.security.ui.AuthenticationEntryPoint; +import org.springframework.security.ui.rememberme.RememberMeServices; import org.springframework.security.util.FilterChainProxy; import org.springframework.util.Assert; @@ -38,11 +41,55 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor ConfigUtils.configureSecurityInterceptor(beanFactory, securityInterceptor); + configureRememberMeSerices(beanFactory); + configureAuthenticationEntryPoint(beanFactory); + configureAuthenticationFilter(beanFactory); + configureFilterChain(beanFactory); } + private void configureRememberMeSerices(ConfigurableListableBeanFactory beanFactory) { + try { + BeanDefinition rememberMeServices = + beanFactory.getBeanDefinition(RememberMeBeanDefinitionParser.DEFAULT_REMEMBER_ME_SERVICES_ID); + rememberMeServices.getPropertyValues().addPropertyValue("userDetailsService", + ConfigUtils.getUserDetailsService(beanFactory)); + + BeanDefinition logoutFilter = + beanFactory.getBeanDefinition(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID); + + } catch (NoSuchBeanDefinitionException e) { + // ignore + } + } + + /** + * Sets the authentication manager, (and remember-me services, if required) on any instances of + * AbstractProcessingFilter + */ + private void configureAuthenticationFilter(ConfigurableListableBeanFactory beanFactory) { + Map beans = beanFactory.getBeansOfType(RememberMeServices.class); + + RememberMeServices rememberMeServices = null; + + if (beans.size() > 0) { + rememberMeServices = (RememberMeServices) beans.values().toArray()[0]; + } + + Iterator authFilters = beanFactory.getBeansOfType(AbstractProcessingFilter.class).values().iterator(); + + while (authFilters.hasNext()) { + AbstractProcessingFilter filter = (AbstractProcessingFilter) authFilters.next(); + + if (rememberMeServices != null) { + logger.info("Using RememberMeServices " + rememberMeServices + " with filter " + filter); + filter.setRememberMeServices(rememberMeServices); + } + } + } + /** * Selects the entry point that should be used in ExceptionTranslationFilter. Strategy is * @@ -52,7 +99,6 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor *
  • throw an exception (for now). TODO: Examine additional beans and types and make decision
  • * * - * @param beanFactory */ private void configureAuthenticationEntryPoint(ConfigurableListableBeanFactory beanFactory) { logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter"); @@ -90,6 +136,15 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor filterMap.put(allUrlsMatch, defaultFilterChain); filterChainProxy.setFilterChainMap(filterMap); + + Map sessionFilters = beanFactory.getBeansOfType(ConcurrentSessionFilter.class); + + if (!sessionFilters.isEmpty()) { + logger.info("Concurrent session filter in use, setting 'forceEagerSessionCreation' to true"); + HttpSessionContextIntegrationFilter scif = (HttpSessionContextIntegrationFilter) + beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_HTTP_SESSION_FILTER_ID); + scif.setForceEagerSessionCreation(true); + } } private List orderFilters(ConfigurableListableBeanFactory beanFactory) { diff --git a/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java new file mode 100644 index 0000000000..f2fdd31934 --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/RememberMeBeanDefinitionParser.java @@ -0,0 +1,72 @@ +package org.springframework.security.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.RuntimeBeanReference; +import org.springframework.beans.factory.support.ManagedList; +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.ui.rememberme.JdbcTokenRepositoryImpl; +import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices; +import org.springframework.security.ui.rememberme.RememberMeProcessingFilter; +import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices; +import org.springframework.security.providers.rememberme.RememberMeAuthenticationProvider; +import org.springframework.util.StringUtils; +import org.w3c.dom.Element; + +/** + * @author Luke Taylor + * @version $Id$ + */ +public class RememberMeBeanDefinitionParser implements BeanDefinitionParser { + protected final Log logger = LogFactory.getLog(getClass()); + + public static final String DEFAULT_REMEMBER_ME_FILTER_ID = "_rememberMeFilter"; + public static final String DEFAULT_REMEMBER_ME_SERVICES_ID = "_rememberMeServices"; + + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinition filter = new RootBeanDefinition(RememberMeProcessingFilter.class); + BeanDefinition services = new RootBeanDefinition(PersistentTokenBasedRememberMeServices.class); + + filter.getPropertyValues().addPropertyValue("authenticationManager", + new RuntimeBeanReference(ConfigUtils.DEFAULT_AUTH_MANAGER_ID)); + + String tokenRepository = element.getAttribute("tokenRepository"); + String dataSource = element.getAttribute("dataSource"); + + if (StringUtils.hasText(tokenRepository)) { + if (StringUtils.hasText(dataSource)) { + throw new SecurityConfigurationException("Specify tokenRepository or dataSource but not both"); + } + + services.getPropertyValues().addPropertyValue("tokenRepository", new RuntimeBeanReference(tokenRepository)); + + } else if (StringUtils.hasText(dataSource)) { + BeanDefinition tokenRepo = new RootBeanDefinition(JdbcTokenRepositoryImpl.class); + tokenRepo.getPropertyValues().addPropertyValue("dataSource", new RuntimeBeanReference(dataSource)); + } else { + // Not persistent + services = new RootBeanDefinition(TokenBasedRememberMeServices.class); + } + + String key = element.getAttribute("key"); + services.getPropertyValues().addPropertyValue("key", key); + + BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext); + BeanDefinition provider = new RootBeanDefinition(RememberMeAuthenticationProvider.class); + provider.getPropertyValues().addPropertyValue("key", key); + + ManagedList providers = (ManagedList) authManager.getPropertyValues().getPropertyValue("providers").getValue(); + providers.add(provider); + + filter.getPropertyValues().addPropertyValue("rememberMeServices", + new RuntimeBeanReference(DEFAULT_REMEMBER_ME_SERVICES_ID)); + + parserContext.getRegistry().registerBeanDefinition(DEFAULT_REMEMBER_ME_SERVICES_ID, services); + parserContext.getRegistry().registerBeanDefinition(DEFAULT_REMEMBER_ME_FILTER_ID, filter); + + return null; + } +} diff --git a/core/src/main/java/org/springframework/security/ui/rememberme/AbstractRememberMeServices.java b/core/src/main/java/org/springframework/security/ui/rememberme/AbstractRememberMeServices.java index 22545e1ffb..946744dd16 100644 --- a/core/src/main/java/org/springframework/security/ui/rememberme/AbstractRememberMeServices.java +++ b/core/src/main/java/org/springframework/security/ui/rememberme/AbstractRememberMeServices.java @@ -3,17 +3,20 @@ package org.springframework.security.ui.rememberme; import org.apache.commons.codec.binary.Base64; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.Authentication; import org.springframework.security.SpringSecurityMessageSource; import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken; import org.springframework.security.ui.AuthenticationDetailsSource; import org.springframework.security.ui.AuthenticationDetailsSourceImpl; +import org.springframework.security.ui.logout.LogoutHandler; import org.springframework.security.userdetails.UserDetails; import org.springframework.security.userdetails.UserDetailsService; import org.springframework.security.userdetails.UsernameNotFoundException; +import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.bind.ServletRequestUtils; -import org.springframework.context.support.MessageSourceAccessor; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -25,7 +28,7 @@ import javax.servlet.http.HttpServletResponse; * @author Luke Taylor * @version $Id$ */ -public abstract class AbstractRememberMeServices implements RememberMeServices { +public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler { protected final Log logger = LogFactory.getLog(getClass()); @@ -42,7 +45,14 @@ public abstract class AbstractRememberMeServices implements RememberMeServices { private String parameter = DEFAULT_PARAMETER; private boolean alwaysRemember; private String key; - private long tokenValiditySeconds = 1209600; // 14 days + private int tokenValiditySeconds = 1209600; // 14 days + + public void afterPropertiesSet() throws Exception { + Assert.hasLength(key); + Assert.hasLength(parameter); + Assert.hasLength(cookieName); + Assert.notNull(userDetailsService); + } /** * Template implementation which locates the Spring Security cookie, decodes it into @@ -261,13 +271,21 @@ public abstract class AbstractRememberMeServices implements RememberMeServices { return cookie; } - protected Cookie makeValidCookie(String value, HttpServletRequest request, long maxAge) { + protected Cookie makeValidCookie(String value, HttpServletRequest request, int maxAge) { Cookie cookie = new Cookie(cookieName, value); - cookie.setMaxAge(new Long(maxAge).intValue()); + cookie.setMaxAge(maxAge); cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/"); return cookie; } + + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + if (logger.isDebugEnabled()) { + logger.debug( "Logout of user " + + (authentication == null ? "Unknown" : authentication.getName())); + } + cancelCookie(request, response); + } public void setCookieName(String cookieName) { this.cookieName = cookieName; @@ -281,6 +299,10 @@ public abstract class AbstractRememberMeServices implements RememberMeServices { this.parameter = parameter; } + public String getParameter() { + return parameter; + } + protected UserDetailsService getUserDetailsService() { return userDetailsService; } @@ -293,11 +315,11 @@ public abstract class AbstractRememberMeServices implements RememberMeServices { this.key = key; } - public void setTokenValiditySeconds(long tokenValiditySeconds) { + public void setTokenValiditySeconds(int tokenValiditySeconds) { this.tokenValiditySeconds = tokenValiditySeconds; } - public long getTokenValiditySeconds() { + public int getTokenValiditySeconds() { return tokenValiditySeconds; } diff --git a/core/src/main/java/org/springframework/security/ui/rememberme/JdbcTokenRepositoryImpl.java b/core/src/main/java/org/springframework/security/ui/rememberme/JdbcTokenRepositoryImpl.java new file mode 100644 index 0000000000..69b868f55f --- /dev/null +++ b/core/src/main/java/org/springframework/security/ui/rememberme/JdbcTokenRepositoryImpl.java @@ -0,0 +1,34 @@ +package org.springframework.security.ui.rememberme; + +import org.springframework.jdbc.core.support.JdbcDaoSupport; + +/** + * + * @author Luke Taylor + * @version $Id$ + */ +public class JdbcTokenRepositoryImpl extends JdbcDaoSupport implements PersistentTokenRepository { + //~ Static fields/initializers ===================================================================================== + public static final String DEF_TOKEN_BY_SERIES_QUERY = + "select username,series,token from persistent_logins where series = ?"; + public static final String DEF_INSERT_TOKEN_STATEMENT = + "insert into persistent_logins (username,series,token) values(?,?,?)"; + public static final String DEF_REMOVE_USER_TOKENS_STATEMENT = + "delete from persistent_logins where username = ?"; + + //~ Instance fields ================================================================================================ + + private String tokensBySeriesQuery = DEF_TOKEN_BY_SERIES_QUERY; + private String insertTokenStatement = DEF_INSERT_TOKEN_STATEMENT; + private String removeUserTokensStatement = DEF_REMOVE_USER_TOKENS_STATEMENT; + + public void saveToken(PersistentRememberMeToken token) { + } + + public PersistentRememberMeToken getTokenForSeries(String seriesId) { + return null; + } + + public void removeAllTokens(String username) { + } +} diff --git a/core/src/main/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServices.java b/core/src/main/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServices.java index 9bc89099a8..894ddb44ce 100644 --- a/core/src/main/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/core/src/main/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServices.java @@ -109,7 +109,10 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe } private PersistentRememberMeToken createNewToken(String username, String series) { - logger.debug("Creating new persistent login token for user " + username); + if (logger.isDebugEnabled()) { + logger.debug(series == null ? "Creating new" : "Renewing" + + " persistent login token for user " + username); + } if (series == null) { byte[] newSeries = new byte[seriesLength]; @@ -131,8 +134,7 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) { String cookieValue = encodeCookie(new String[] {token.getSeries(), token.getTokenValue()}); - long maxAge = System.currentTimeMillis() + getTokenValiditySeconds() * 1000; - response.addCookie(makeValidCookie(cookieValue, request, maxAge)); + response.addCookie(makeValidCookie(cookieValue, request, getTokenValiditySeconds())); } public void setTokenRepository(PersistentTokenRepository tokenRepository) { diff --git a/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java b/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java index b1d010b981..7a0b422ecf 100644 --- a/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java +++ b/core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java @@ -4,6 +4,7 @@ import org.springframework.security.AuthenticationException; import org.springframework.security.ui.AbstractProcessingFilter; import org.springframework.security.ui.FilterChainOrderUtils; import org.springframework.security.ui.SpringSecurityFilter; +import org.springframework.security.ui.rememberme.AbstractRememberMeServices; import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices; import org.springframework.util.StringUtils; @@ -35,8 +36,8 @@ public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter { usernameParameter = authFilter.getUsernameParameter(); passwordParameter = authFilter.getPasswordParameter(); - if (authFilter.getRememberMeServices() instanceof TokenBasedRememberMeServices) { - rememberMeParameter = ((TokenBasedRememberMeServices)authFilter.getRememberMeServices()).getParameter(); + if (authFilter.getRememberMeServices() instanceof AbstractRememberMeServices) { + rememberMeParameter = ((AbstractRememberMeServices)authFilter.getRememberMeServices()).getParameter(); } } diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc index 79e2e8f300..21ad670c9e 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc @@ -48,7 +48,7 @@ protect.attlist &= http = ## Container element for HTTP security configuration - element http {http.attlist, intercept-url+, form-login?, http-basic?, logout?, concurrent-session-control? } + element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me?) } http.attlist &= ## Controls the eagerness with which an HTTP session is created. [ a:defaultValue = "ifRequired" ] attribute createSession {"ifRequired" | "always" | "never" }? @@ -135,6 +135,13 @@ concurrent-sessions.attlist &= concurrent-sessions.attlist &= attribute exceptionIfMaximumExceeded {"true" | "false"}? +remember-me = + element remember-me {remember-me.attlist} +remember-me.attlist &= + attribute key {xsd:string} +remember-me.attlist &= + (attribute tokenRepository {xsd:string} | attribute datasource {xsd:string})? + authentication-provider = element authentication-provider {authentication-provider.attlist, (user-service | jdbc-user-service)} authentication-provider.attlist &= empty @@ -144,7 +151,6 @@ user-service = user-service.attlist &= attribute properties {xsd:string}* - user = element user {user.attlist, empty} user.attlist &= diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd index adfb1a6b03..8fbac3a4ae 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd @@ -72,13 +72,14 @@ Container element for HTTP security configuration - - - - - - - + + + + + + + + @@ -241,6 +242,16 @@ + + + + + + + + + + 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 d34faaa6a6..fddfaa923c 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -1,20 +1,22 @@ package org.springframework.security.config; -import org.junit.AfterClass; -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; +import org.springframework.security.ui.rememberme.RememberMeProcessingFilter; import org.springframework.security.ui.basicauth.BasicProcessingFilter; import org.springframework.security.ui.logout.LogoutFilter; import org.springframework.security.ui.webapp.AuthenticationProcessingFilter; import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; import org.springframework.security.util.FilterChainProxy; +import org.junit.AfterClass; +import static org.junit.Assert.*; +import org.junit.BeforeClass; +import org.junit.Test; + import java.util.Iterator; import java.util.List; @@ -54,7 +56,7 @@ public class HttpSecurityBeanDefinitionParserTests { List filterList = filterChainProxy.getFilters("/someurl"); - assertTrue("Expected 8 filters in chain", filterList.size() == 8); + assertEquals("Expected 9 filters in chain", 9, filterList.size()); Iterator filters = filterList.iterator(); @@ -64,6 +66,7 @@ public class HttpSecurityBeanDefinitionParserTests { assertTrue(filters.next() instanceof AuthenticationProcessingFilter); assertTrue(filters.next() instanceof DefaultLoginPageGeneratingFilter); assertTrue(filters.next() instanceof BasicProcessingFilter); + assertTrue(filters.next() instanceof RememberMeProcessingFilter); assertTrue(filters.next() instanceof ExceptionTranslationFilter); assertTrue(filters.next() instanceof FilterSecurityInterceptor); } diff --git a/core/src/test/resources/org/springframework/security/config/http-security.xml b/core/src/test/resources/org/springframework/security/config/http-security.xml index ba978e5ff5..60c5933b80 100644 --- a/core/src/test/resources/org/springframework/security/config/http-security.xml +++ b/core/src/test/resources/org/springframework/security/config/http-security.xml @@ -7,20 +7,22 @@ http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd"> - + - + - + + + @@ -29,4 +31,9 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc + + + + + \ No newline at end of file diff --git a/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml b/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml index 9134b5d509..91452ea7ef 100644 --- a/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml +++ b/samples/tutorial/src/main/webapp/WEB-INF/applicationContext-security-ns.xml @@ -22,8 +22,10 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc + +