From 0e7dac6ca56fc39ee6561672590128b95512005a Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Sat, 10 Nov 2007 19:20:36 +0000 Subject: [PATCH] SEC-565: Refactoring of TokenBasedRememberMeServices. Changed arguments to makeValidSignature so that it could be used from both places where a signature is required and refactored the class to extend AbstractRememberMeServices. The method processAutoLoginCookie now returns a UserDetails, rather than username, as the UserDetails is needed in TokenBasedRememberMeServices. --- .../AbstractRememberMeServices.java | 43 +- .../ui/rememberme/InvalidCookieException.java | 3 + ...ersistentTokenBasedRememberMeServices.java | 14 +- .../TokenBasedRememberMeServices.java | 402 ++++-------------- .../AbstractRememberMeServicesTests.java | 40 +- ...tentTokenBasedRememberMeServicesTests.java | 12 +- .../TokenBasedRememberMeServicesTests.java | 43 +- 7 files changed, 161 insertions(+), 396 deletions(-) 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 6b94763c66..40599ffdaa 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 @@ -29,19 +29,23 @@ import javax.servlet.http.HttpServletResponse; * @version $Id$ */ public abstract class AbstractRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler { + //~ Static fields/initializers ===================================================================================== + public static final String SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY = "SPRING_SECURITY_REMEMBER_ME_COOKIE"; + public static final String DEFAULT_PARAMETER = "_spring_security_remember_me"; + + private static final String DELIMITER = ":"; + + //~ Instance fields ================================================================================================ protected final Log logger = LogFactory.getLog(getClass()); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - public static final String DEFAULT_PARAMETER = "_spring_security_remember_me"; - public static final String SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY = "SPRING_SECURITY_REMEMBER_ME_COOKIE"; - private static final String DELIMITER = ":"; private UserDetailsService userDetailsService; private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl(); - private String cookieName = SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY; + private String cookieName = SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY; private String parameter = DEFAULT_PARAMETER; private boolean alwaysRemember; private String key; @@ -75,8 +79,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, try { String[] cookieTokens = decodeCookie(rememberMeCookie); - String username = processAutoLoginCookie(cookieTokens, request, response); - user = loadAndValidateUserDetails(username); + user = processAutoLoginCookie(cookieTokens, request, response); } catch (CookieTheftException cte) { cancelCookie(request, response); throw cte; @@ -172,22 +175,23 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, return sb.toString(); } - protected UserDetails loadAndValidateUserDetails(String username) throws UsernameNotFoundException, + /** + * Provided for subclass convenience to check the account status of a loaded user. + * + * @throws UsernameNotFoundException if the username could not be located by the configured UserDetailsService. + * @throws RememberMeAuthenticationException if the account is locked or disabled. + */ + protected void validateUserDetails(UserDetails user) throws UsernameNotFoundException, RememberMeAuthenticationException { - UserDetails user; - - user = this.userDetailsService.loadUserByUsername(username); - if (!user.isAccountNonExpired() || !user.isCredentialsNonExpired() || !user.isEnabled()) { throw new RememberMeAuthenticationException("Remember-me login was valid for user " + user.getUsername() + ", but account is expired, has expired credentials or is disabled"); } - - return user; } public final void loginFail(HttpServletRequest request, HttpServletResponse response) { + logger.debug("Interactive login attempt was unsuccessful."); cancelCookie(request, response); onLoginFail(request, response); } @@ -202,6 +206,7 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, Authentication successfulAuthentication) { if (!rememberMeRequested(request, parameter)) { + logger.debug("Remember-me login not requested."); return; } @@ -250,12 +255,14 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, * @param cookieTokens the decoded and tokenized cookie value * @param request the request * @param response the response, to allow the cookie to be modified if required. - * @return the name of the corresponding user account if the cookie was validated successfully. + * @return the UserDetails for the corresponding user account if the cookie was validated successfully. * @throws RememberMeAuthenticationException if the cookie is invalid or the login is invalid for some * other reason. + * @throws UsernameNotFoundException if the user account corresponding to the login cookie couldn't be found + * (for example if the user has been removed from the system). */ - protected abstract String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, - HttpServletResponse response) throws RememberMeAuthenticationException; + protected abstract UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, + HttpServletResponse response) throws RememberMeAuthenticationException, UsernameNotFoundException; protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) { logger.debug("Cancelling cookie"); @@ -315,6 +322,10 @@ public abstract class AbstractRememberMeServices implements RememberMeServices, this.key = key; } + public String getKey() { + return key; + } + public void setTokenValiditySeconds(int tokenValiditySeconds) { this.tokenValiditySeconds = tokenValiditySeconds; } diff --git a/core/src/main/java/org/springframework/security/ui/rememberme/InvalidCookieException.java b/core/src/main/java/org/springframework/security/ui/rememberme/InvalidCookieException.java index 335001a61a..3440b9d796 100644 --- a/core/src/main/java/org/springframework/security/ui/rememberme/InvalidCookieException.java +++ b/core/src/main/java/org/springframework/security/ui/rememberme/InvalidCookieException.java @@ -1,6 +1,9 @@ package org.springframework.security.ui.rememberme; /** + * Exception thrown by a RememberMeServices implementation to indicate + * that a submitted cookie is of an invalid format or has expired. + * * @author Luke Taylor * @version $Id$ */ 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 5a9e3c195d..ba460f99e2 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 @@ -1,8 +1,10 @@ package org.springframework.security.ui.rememberme; -import org.apache.commons.codec.binary.Base64; -import org.springframework.dao.DataAccessException; import org.springframework.security.Authentication; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.dao.DataAccessException; + +import org.apache.commons.codec.binary.Base64; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -61,7 +63,7 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe * @throws CookieTheftException if a presented series value is found, but the stored token is different from the * one presented. */ - protected String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { + protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { if (cookieTokens.length != 2) { throw new InvalidCookieException("Cookie token did not contain " + 2 + @@ -108,7 +110,11 @@ public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeSe throw new RememberMeAuthenticationException("Autologin failed due to data access problem"); } - return token.getUsername(); + UserDetails user = getUserDetailsService().loadUserByUsername(token.getUsername()); + + validateUserDetails(user); + + return user; } /** diff --git a/core/src/main/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServices.java b/core/src/main/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServices.java index ca9ad8b5bf..a9c6c2133d 100644 --- a/core/src/main/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServices.java +++ b/core/src/main/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServices.java @@ -15,28 +15,16 @@ package org.springframework.security.ui.rememberme; -import java.util.Date; +import org.springframework.security.Authentication; +import org.springframework.security.userdetails.UserDetails; +import org.springframework.util.StringUtils; + +import org.apache.commons.codec.digest.DigestUtils; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import org.springframework.security.Authentication; -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.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.digest.DigestUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.RequestUtils; +import java.util.Arrays; +import java.util.Date; /** * Identifies previously remembered users by a Base-64 encoded cookie. @@ -44,8 +32,7 @@ import org.springframework.web.bind.RequestUtils; *

* This implementation does not rely on an external database, so is attractive * for simple applications. The cookie will be valid for a specific period from - * the date of the last - * {@link #loginSuccess(HttpServletRequest, HttpServletResponse, Authentication)}. + * the date of the last {@link #loginSuccess(HttpServletRequest, HttpServletResponse, Authentication)}. * As per the interface contract, this method will only be called when the * principal completes a successful interactive authentication. As such the time * period commences from the last authentication attempt where they furnished @@ -72,7 +59,7 @@ import org.springframework.web.bind.RequestUtils; * *

*

- * As such, if the user changes their password any remember-me token will be + * As such, if the user changes their password, any remember-me token will be * invalidated. Equally, the system administrator may invalidate every * remember-me token on issue by changing the key. This provides some reasonable * approaches to recovering from a remember-me token being left on a public @@ -86,366 +73,123 @@ import org.springframework.web.bind.RequestUtils; *

* This is a basic remember-me implementation which is suitable for many * applications. However, we recommend a database-based implementation if you - * require a more secure remember-me approach. + * require a more secure remember-me approach (see {@link PersistentTokenBasedRememberMeServices}). *

*

* By default the tokens will be valid for 14 days from the last successful * authentication attempt. This can be changed using - * {@link #setTokenValiditySeconds(long)}. + * {@link #setTokenValiditySeconds(int)}. *

* * @author Ben Alex * @version $Id$ */ -public class TokenBasedRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler { - //~ Static fields/initializers ===================================================================================== - - public static final String SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = "SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE"; - - public static final String DEFAULT_PARAMETER = "_spring_security_remember_me"; - - protected static final Log logger = LogFactory.getLog(TokenBasedRememberMeServices.class); - - //~ Instance fields ================================================================================================ - - protected AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl(); - - private String key; - - private String parameter = DEFAULT_PARAMETER; - - private UserDetailsService userDetailsService; - - protected long tokenValiditySeconds = 1209600; // 14 days - - private boolean alwaysRemember = false; - - private String cookieName = SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY; +public class TokenBasedRememberMeServices extends AbstractRememberMeServices { //~ Methods ======================================================================================================== - public void afterPropertiesSet() throws Exception { - Assert.hasLength(key); - Assert.hasLength(parameter); - Assert.hasLength(cookieName); - Assert.notNull(userDetailsService); - } + public UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, + HttpServletResponse response) { - public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { - Cookie[] cookies = request.getCookies(); - - if ((cookies == null) || (cookies.length == 0)) { - return null; - } - - for (int i = 0; i < cookies.length; i++) { - if (!cookieName.equals(cookies[i].getName())) { - continue; - } - - // We have the spring security cookie - String cookieValue = cookies[i].getValue(); - - if (logger.isDebugEnabled()) { - logger.debug("Remember-me cookie detected"); - } - - for (int j = 0; j < cookieValue.length() % 4; j++) { - cookieValue = cookieValue + "="; - } - - if (!Base64.isArrayByteBase64(cookieValue.getBytes())) { - cancelCookie(request, response, "Cookie token was not Base64 encoded; value was '" + cookieValue + "'"); - - return null; - } - - // Decode token from Base64 - // format of token is: - // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key) - String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes())); - String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText, ":"); - - if (cookieTokens.length != 3) { - cancelCookie(request, response, "Cookie token did not contain 3 tokens; decoded value was '" - + cookieAsPlainText + "'"); - - return null; - } - - long tokenExpiryTime; - - try { - tokenExpiryTime = new Long(cookieTokens[1]).longValue(); - } - catch (NumberFormatException nfe) { - cancelCookie(request, response, - "Cookie token[1] did not contain a valid number (contained '" + cookieTokens[1] + "')"); - - return null; - } - - if (isTokenExpired(tokenExpiryTime)) { - cancelCookie(request, response, "Cookie token[1] has expired (expired on '" - + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')"); - - return null; - } - - // Check the user exists - // Defer lookup until after expiry time checked, to - // possibly avoid expensive lookup - UserDetails userDetails = loadUserDetails(request, response, cookieTokens); - - if (userDetails == null) { - cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0] - + "' but was not found"); - return null; - } - - if (!isValidUserDetails(request, response, userDetails, cookieTokens)) { - return null; - } - - // Check signature of token matches remaining details - // Must do this after user lookup, as we need the - // DAO-derived password - // If efficiency was a major issue, just add in a - // UserCache implementation, - // but recall this method is usually only called one per - // HttpSession - // (as if the token is valid, it will cause - // SecurityContextHolder population, whilst - // if invalid, will cause the cookie to be cancelled) - String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails); - - if (!expectedTokenSignature.equals(cookieTokens[2])) { - cancelCookie(request, response, "Cookie token[2] contained signature '" + cookieTokens[2] - + "' but expected '" + expectedTokenSignature + "'"); - - return null; - } - - // By this stage we have a valid token - if (logger.isDebugEnabled()) { - logger.debug("Remember-me cookie accepted"); - } - - RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, userDetails, - userDetails.getAuthorities()); - auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request)); - - return auth; + if (cookieTokens.length != 3) { + throw new InvalidCookieException("Cookie token did not contain " + 2 + + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'"); } + long tokenExpiryTime; - return null; - } + try { + tokenExpiryTime = new Long(cookieTokens[1]).longValue(); + } + catch (NumberFormatException nfe) { + throw new InvalidCookieException("Cookie token[1] did not contain a valid number (contained '" + + cookieTokens[1] + "')"); + } - /** - * @param tokenExpiryTime - * @param userDetails - * @return - */ - protected String makeTokenSignature(long tokenExpiryTime, UserDetails userDetails) { - String expectedTokenSignature = DigestUtils.md5Hex(userDetails.getUsername() + ":" + tokenExpiryTime + ":" - + userDetails.getPassword() + ":" + this.key); - return expectedTokenSignature; - } + if (isTokenExpired(tokenExpiryTime)) { + throw new InvalidCookieException("Cookie token[1] has expired (expired on '" + + new Date(tokenExpiryTime) + "'; current time is '" + new Date() + "')"); + } - protected boolean isValidUserDetails(HttpServletRequest request, HttpServletResponse response, - UserDetails userDetails, String[] cookieTokens) { - // Immediately reject if the user is not allowed to - // login - if (!userDetails.isAccountNonExpired() || !userDetails.isCredentialsNonExpired() || !userDetails.isEnabled()) { - cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0] - + "' but account has expired, credentials have expired, or user is disabled"); + // Check the user exists. + // Defer lookup until after expiry time checked, to possibly avoid expensive database call. - return false; - } - return true; - } + UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]); - protected UserDetails loadUserDetails(HttpServletRequest request, HttpServletResponse response, - String[] cookieTokens) { - UserDetails userDetails = null; + validateUserDetails(userDetails); - try { - userDetails = this.userDetailsService.loadUserByUsername(cookieTokens[0]); - } - catch (UsernameNotFoundException notFound) { - cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0] - + "' but was not found"); + // Check signature of token matches remaining details. + // Must do this after user lookup, as we need the DAO-derived password. + // If efficiency was a major issue, just add in a UserCache implementation, + // but recall that this method is usually only called once per HttpSession - if the token is valid, + // it will cause SecurityContextHolder population, whilst if invalid, will cause the cookie to be cancelled. + String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails.getUsername(), + userDetails.getPassword()); - return null; - } - return userDetails; + if (!expectedTokenSignature.equals(cookieTokens[2])) { + throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2] + + "' but expected '" + expectedTokenSignature + "'"); + } + + return userDetails; + } + + /** + * Calculates the digital signature to be put in the cookie. Default value is + * MD5 ("username:tokenExpiryTime:password:key") + */ + protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { + return DigestUtils.md5Hex(username + ":" + tokenExpiryTime + ":" + password + ":" + getKey()); } protected boolean isTokenExpired(long tokenExpiryTime) { - // Check it has not expired - if (tokenExpiryTime < System.currentTimeMillis()) { - return true; - } - return false; + return tokenExpiryTime < System.currentTimeMillis(); } - protected void cancelCookie(HttpServletRequest request, HttpServletResponse response, String reasonForLog) { - if ((reasonForLog != null) && logger.isDebugEnabled()) { - logger.debug("Cancelling cookie for reason: " + reasonForLog); - } - - response.addCookie(makeCancelCookie(request)); - } - - public String getKey() { - return key; - } - - public String getParameter() { - return parameter; - } - - public long getTokenValiditySeconds() { - return tokenValiditySeconds; - } - - public UserDetailsService getUserDetailsService() { - return userDetailsService; - } - - public void loginFail(HttpServletRequest request, HttpServletResponse response) { - cancelCookie(request, response, "Interactive authentication attempt was unsuccessful"); - } - - protected boolean rememberMeRequested(HttpServletRequest request, String parameter) { - if (alwaysRemember) { - return true; - } - - return RequestUtils.getBooleanParameter(request, parameter, false); - } - - public void loginSuccess(HttpServletRequest request, HttpServletResponse response, + public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { - // Exit if the principal hasn't asked to be remembered - if (!rememberMeRequested(request, parameter)) { - if (logger.isDebugEnabled()) { - logger.debug("Did not send remember-me cookie (principal did not set parameter '" + this.parameter - + "')"); - } - - return; - } - - // Determine username and password, ensuring empty strings - Assert.notNull(successfulAuthentication.getPrincipal()); - Assert.notNull(successfulAuthentication.getCredentials()); String username = retrieveUserName(successfulAuthentication); String password = retrievePassword(successfulAuthentication); - // If unable to find a username and password, just abort as - // TokenBasedRememberMeServices unable to construct a valid token in - // this case + // If unable to find a username and password, just abort as TokenBasedRememberMeServices is + // unable to construct a valid token in this case. if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) { return; } - long expiryTime = System.currentTimeMillis() + (tokenValiditySeconds * 1000); + long expiryTime = System.currentTimeMillis() + getTokenValiditySeconds() * 1000; - // construct token to put in cookie; format is: - // username + ":" + expiryTime + ":" + Md5Hex(username + ":" + - // expiryTime + ":" + password + ":" + key) - String signatureValue = DigestUtils.md5Hex(username + ":" + expiryTime + ":" + password + ":" + key); - String tokenValue = username + ":" + expiryTime + ":" + signatureValue; - String tokenValueBase64 = new String(Base64.encodeBase64(tokenValue.getBytes())); - response.addCookie(makeValidCookie(tokenValueBase64, request, tokenValiditySeconds)); + String signatureValue = makeTokenSignature(expiryTime, username, password); + String cookieValue = encodeCookie(new String[] {username, Long.toString(expiryTime), signatureValue}); + + response.addCookie(makeValidCookie(cookieValue, request, getTokenValiditySeconds())); if (logger.isDebugEnabled()) { - logger.debug("Added remember-me cookie for user '" - + username + "', expiry: '" + new Date(expiryTime) + "'"); + logger.debug("Added remember-me cookie for user '" + username + "', expiry: '" + + new Date(expiryTime) + "'"); } } - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - cancelCookie(request, response, "Logout of user " - + (authentication == null ? "Unknown" : authentication.getName())); - } - - protected String retrieveUserName(Authentication successfulAuthentication) { - if (isInstanceOfUserDetails(successfulAuthentication)) { - return ((UserDetails) successfulAuthentication.getPrincipal()).getUsername(); + protected String retrieveUserName(Authentication authentication) { + if (isInstanceOfUserDetails(authentication)) { + return ((UserDetails) authentication.getPrincipal()).getUsername(); } else { - return successfulAuthentication.getPrincipal().toString(); + return authentication.getPrincipal().toString(); } } - protected String retrievePassword(Authentication successfulAuthentication) { - if (isInstanceOfUserDetails(successfulAuthentication)) { - return ((UserDetails) successfulAuthentication.getPrincipal()).getPassword(); + protected String retrievePassword(Authentication authentication) { + if (isInstanceOfUserDetails(authentication)) { + return ((UserDetails) authentication.getPrincipal()).getPassword(); } else { - return successfulAuthentication.getCredentials().toString(); + return authentication.getCredentials().toString(); } } private boolean isInstanceOfUserDetails(Authentication authentication) { return authentication.getPrincipal() instanceof UserDetails; } - - protected Cookie makeCancelCookie(HttpServletRequest request) { - Cookie cookie = new Cookie(cookieName, null); - cookie.setMaxAge(0); - cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/"); - - return cookie; - } - - protected Cookie makeValidCookie(String tokenValueBase64, HttpServletRequest request, long maxAge) { - Cookie cookie = new Cookie(cookieName, tokenValueBase64); - cookie.setMaxAge(new Long(maxAge).intValue()); - cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/"); - - return cookie; - } - - public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { - Assert.notNull(authenticationDetailsSource, "AuthenticationDetailsSource required"); - this.authenticationDetailsSource = authenticationDetailsSource; - } - - public void setKey(String key) { - this.key = key; - } - - public void setParameter(String parameter) { - this.parameter = parameter; - } - - public void setCookieName(String cookieName) { - this.cookieName = cookieName; - } - - public void setTokenValiditySeconds(long tokenValiditySeconds) { - this.tokenValiditySeconds = tokenValiditySeconds; - } - - public void setUserDetailsService(UserDetailsService userDetailsService) { - this.userDetailsService = userDetailsService; - } - - public boolean isAlwaysRemember() { - return alwaysRemember; - } - - public void setAlwaysRemember(boolean alwaysRemember) { - this.alwaysRemember = alwaysRemember; - } - - public String getCookieName() { - return cookieName; - } - } diff --git a/core/src/test/java/org/springframework/security/ui/rememberme/AbstractRememberMeServicesTests.java b/core/src/test/java/org/springframework/security/ui/rememberme/AbstractRememberMeServicesTests.java index f5839d9f80..77393ce7b7 100644 --- a/core/src/test/java/org/springframework/security/ui/rememberme/AbstractRememberMeServicesTests.java +++ b/core/src/test/java/org/springframework/security/ui/rememberme/AbstractRememberMeServicesTests.java @@ -24,7 +24,7 @@ import javax.servlet.http.HttpServletResponse; * @version $Id$ */ public class AbstractRememberMeServicesTests { - User joe = new User("joe", "password", true, true,true,true, new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")}); + static User joe = new User("joe", "password", true, true,true,true, new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")}); @Test(expected = InvalidCookieException.class) public void nonBase64CookieShouldBeDetected() { @@ -37,7 +37,7 @@ public class AbstractRememberMeServicesTests { MockRememberMeServices services = new MockRememberMeServices(); String encoded = services.encodeCookie(cookie); - // '=' aren't alowed in version 0 cookies. + // '=' aren't alowed in version 0 cookies. assertFalse(encoded.endsWith("=")); String[] decoded = services.decodeCookie(encoded); @@ -45,7 +45,7 @@ public class AbstractRememberMeServicesTests { assertEquals("the", decoded[0]); assertEquals("cookie", decoded[1]); assertEquals("tokens", decoded[2]); - assertEquals("blah", decoded[3]); + assertEquals("blah", decoded[3]); } @Test @@ -57,20 +57,20 @@ public class AbstractRememberMeServicesTests { assertNull(services.autoLogin(request, response)); // shouldn't try to invalidate our cookie - assertNull(response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY)); + assertNull(response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)); request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); // set non-login cookie request.setCookies(new Cookie[] {new Cookie("mycookie", "cookie")}); assertNull(services.autoLogin(request, response)); - assertNull(response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY)); + assertNull(response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)); } @Test public void successfulAutoLoginReturnsExpectedAuthentication() { MockRememberMeServices services = new MockRememberMeServices(); - services.setUserDetailsService(new MockAuthenticationDao(joe, false)); + services.setUserDetailsService(new MockUserDetailsService(joe, false)); assertNotNull(services.getUserDetailsService()); MockHttpServletRequest request = new MockHttpServletRequest(); @@ -86,7 +86,7 @@ public class AbstractRememberMeServicesTests { @Test public void autoLoginShouldFailIfInvalidCookieExceptionIsRaised() { MockRememberMeServices services = new MockRememberMeServices(); - services.setUserDetailsService(new MockAuthenticationDao(joe, true)); + services.setUserDetailsService(new MockUserDetailsService(joe, true)); MockHttpServletRequest request = new MockHttpServletRequest(); // Wrong number of tokes @@ -103,7 +103,7 @@ public class AbstractRememberMeServicesTests { @Test public void autoLoginShouldFailIfUserNotFound() { MockRememberMeServices services = new MockRememberMeServices(); - services.setUserDetailsService(new MockAuthenticationDao(joe, true)); + services.setUserDetailsService(new MockUserDetailsService(joe, true)); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); @@ -120,7 +120,7 @@ public class AbstractRememberMeServicesTests { public void autoLoginShouldFailIfUserAccountIsLocked() { MockRememberMeServices services = new MockRememberMeServices(); User joeLocked = new User("joe", "password",false,true,true,true,joe.getAuthorities()); - services.setUserDetailsService(new MockAuthenticationDao(joeLocked, false)); + services.setUserDetailsService(new MockUserDetailsService(joeLocked, false)); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); @@ -136,7 +136,7 @@ public class AbstractRememberMeServicesTests { @Test public void loginFailShouldCancelCookie() { MockRememberMeServices services = new MockRememberMeServices(); - services.setUserDetailsService(new MockAuthenticationDao(joe, true)); + services.setUserDetailsService(new MockUserDetailsService(joe, true)); MockHttpServletRequest request = new MockHttpServletRequest(); request.setContextPath("contextpath"); @@ -151,12 +151,12 @@ public class AbstractRememberMeServicesTests { @Test(expected = CookieTheftException.class) public void cookieTheftExceptionShouldBeRethrown() { MockRememberMeServices services = new MockRememberMeServices() { - protected String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { + protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { throw new CookieTheftException("Pretending cookie was stolen"); } }; - services.setUserDetailsService(new MockAuthenticationDao(joe, false)); + services.setUserDetailsService(new MockUserDetailsService(joe, false)); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(createLoginCookie("cookie:1:2")); @@ -203,7 +203,6 @@ public class AbstractRememberMeServicesTests { services.setAlwaysRemember(true); services.loginSuccess(request, response, auth); assertTrue(services.loginSuccessCalled); - } @Test @@ -222,14 +221,14 @@ public class AbstractRememberMeServicesTests { private Cookie[] createLoginCookie(String cookieToken) { MockRememberMeServices services = new MockRememberMeServices(); - Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, services.encodeCookie(StringUtils.delimitedListToStringArray(cookieToken, ":"))); return new Cookie[] {cookie}; } private void assertCookieCancelled(MockHttpServletResponse response) { - Cookie returnedCookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -247,20 +246,23 @@ public class AbstractRememberMeServicesTests { loginSuccessCalled = true; } - protected String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException { + protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException { if(cookieTokens.length != 3) { throw new InvalidCookieException("deliberate exception"); } - return "joe"; + UserDetails user = getUserDetailsService().loadUserByUsername("joe"); + validateUserDetails(user); + + return user; } } - private class MockAuthenticationDao implements UserDetailsService { + public static class MockUserDetailsService implements UserDetailsService { private UserDetails toReturn; private boolean throwException; - public MockAuthenticationDao(UserDetails toReturn, boolean throwException) { + public MockUserDetailsService(UserDetails toReturn, boolean throwException) { this.toReturn = toReturn; this.throwException = throwException; } diff --git a/core/src/test/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServicesTests.java b/core/src/test/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServicesTests.java index 0573bdaeb4..ea9c1df49c 100644 --- a/core/src/test/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServicesTests.java +++ b/core/src/test/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServicesTests.java @@ -1,11 +1,13 @@ package org.springframework.security.ui.rememberme; -import static org.junit.Assert.assertEquals; -import org.junit.Before; -import org.junit.Test; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import org.junit.Before; +import org.junit.Test; import java.util.Date; @@ -20,6 +22,8 @@ public class PersistentTokenBasedRememberMeServicesTests { public void setUpData() throws Exception { services = new PersistentTokenBasedRememberMeServices(); services.setCookieName("mycookiename"); + services.setUserDetailsService( + new AbstractRememberMeServicesTests.MockUserDetailsService(AbstractRememberMeServicesTests.joe, false)); } @Test(expected = InvalidCookieException.class) diff --git a/core/src/test/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServicesTests.java b/core/src/test/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServicesTests.java index 706f8398f3..1f9d04e5ce 100644 --- a/core/src/test/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServicesTests.java +++ b/core/src/test/java/org/springframework/security/ui/rememberme/TokenBasedRememberMeServicesTests.java @@ -53,7 +53,6 @@ public class TokenBasedRememberMeServicesTests extends TestCase { //~ Constructors =================================================================================================== public TokenBasedRememberMeServicesTests() { - super(); } public TokenBasedRememberMeServicesTests(String arg0) { @@ -85,10 +84,6 @@ public class TokenBasedRememberMeServicesTests extends TestCase { return tokenValueBase64; } - public static void main(String[] args) { - junit.textui.TestRunner.run(TokenBasedRememberMeServicesTests.class); - } - public void testAutoLoginIfDoesNotPresentAnyCookies() throws Exception { TokenBasedRememberMeServices services = new TokenBasedRememberMeServices(); @@ -105,7 +100,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNull(returnedCookie); // shouldn't try to invalidate our cookie } @@ -126,7 +121,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNull(returnedCookie); // shouldn't try to invalidate our cookie } @@ -139,7 +134,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); // services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, generateCorrectCookieContentForToken(System.currentTimeMillis() - 1000000, "someone", "password", "key")); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -150,7 +145,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -165,7 +160,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); //services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, new String(Base64.encodeBase64("x".getBytes()))); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -176,7 +171,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -190,7 +185,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); //services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, "NOT_BASE_64_ENCODED"); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -201,7 +196,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -216,7 +211,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); //services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password", "WRONG_KEY")); MockHttpServletRequest request = new MockHttpServletRequest(); @@ -228,7 +223,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -243,7 +238,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); //services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, new String(Base64.encodeBase64("username:NOT_A_NUMBER:signature".getBytes()))); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -254,7 +249,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -265,7 +260,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(null, true)); //services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password", "key")); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -276,7 +271,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { assertNull(result); - Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie returnedCookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(returnedCookie); assertEquals(0, returnedCookie.getMaxAge()); } @@ -290,7 +285,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { services.setUserDetailsService(new MockAuthenticationDao(user, false)); // services.afterPropertiesSet(); - Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, generateCorrectCookieContentForToken(System.currentTimeMillis() + 1000000, "someone", "password", "key")); MockHttpServletRequest request = new MockHttpServletRequest(); request.setCookies(new Cookie[] {cookie}); @@ -330,7 +325,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { MockHttpServletResponse response = new MockHttpServletResponse(); services.loginFail(request, response); - Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(cookie); assertEquals(0, cookie.getMaxAge()); } @@ -346,7 +341,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { new TestingAuthenticationToken("someone", "password", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); - Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNull(cookie); } @@ -361,7 +356,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { new TestingAuthenticationToken("someone", "password", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); - Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(cookie); assertEquals(services.getTokenValiditySeconds(), cookie.getMaxAge()); assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes())); @@ -381,7 +376,7 @@ public class TokenBasedRememberMeServicesTests extends TestCase { new TestingAuthenticationToken(user, "ignored", new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")})); - Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + Cookie cookie = response.getCookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY); assertNotNull(cookie); assertEquals(services.getTokenValiditySeconds(), cookie.getMaxAge()); assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes()));