Procházet zdrojové kódy

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.

Luke Taylor před 18 roky
rodič
revize
0e7dac6ca5

+ 27 - 16
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;
     }

+ 3 - 0
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$
  */

+ 10 - 4
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;
     }
 
     /**

+ 71 - 327
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 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 org.apache.commons.codec.digest.DigestUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+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;
  * <p>
  * 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;
  *
  * </p>
  * <p>
- * 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;
  * <p>
  * 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}).
  * </p>
  * <p>
  * 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)}.
  * </p>
  *
  * @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 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;
-            }
+    public UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
+            HttpServletResponse response) {
 
-            // 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");
-            }
+        if (cookieTokens.length != 3) {
+            throw new InvalidCookieException("Cookie token did not contain " + 2 +
+                    " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
+        }
 
-            RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.key, userDetails,
-                    userDetails.getAuthorities());
-            auth.setDetails(authenticationDetailsSource.buildDetails((HttpServletRequest) request));
+        long tokenExpiryTime;
 
-            return auth;
+        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] + "')");
         }
 
+        if (isTokenExpired(tokenExpiryTime)) {
+            throw new InvalidCookieException("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 database call.
 
-	/**
-	 * @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;
-	}
+        UserDetails userDetails = getUserDetailsService().loadUserByUsername(cookieTokens[0]);
 
-	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");
+        validateUserDetails(userDetails);
 
-			return false;
-		}
-		return true;
-	}
+        // 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());
 
-	protected UserDetails loadUserDetails(HttpServletRequest request, HttpServletResponse response,
-			String[] cookieTokens) {
-		UserDetails userDetails = null;
+        if (!expectedTokenSignature.equals(cookieTokens[2])) {
+            throw new InvalidCookieException("Cookie token[2] contained signature '" + cookieTokens[2]
+                    + "' but expected '" + expectedTokenSignature + "'");
+        }
 
-		try {
-			userDetails = this.userDetailsService.loadUserByUsername(cookieTokens[0]);
-		}
-		catch (UsernameNotFoundException notFound) {
-			cancelCookie(request, response, "Cookie token[0] contained username '" + cookieTokens[0]
-					+ "' but was not found");
+        return userDetails;
+    }
 
-			return null;
-		}
-		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;
-	}
-
-	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;
+		return tokenExpiryTime < System.currentTimeMillis();
 	}
 
-	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;
+
+		String signatureValue = makeTokenSignature(expiryTime, username, password);
+        String cookieValue = encodeCookie(new String[] {username, Long.toString(expiryTime), signatureValue});
 
-		// 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));
+		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;
-	}
-
 }

+ 21 - 19
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;
         }

+ 7 - 3
core/src/test/java/org/springframework/security/ui/rememberme/PersistentTokenBasedRememberMeServicesTests.java

@@ -1,11 +1,13 @@
 package org.springframework.security.ui.rememberme;
 
+import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import org.junit.Before;
 import org.junit.Test;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
 
 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)

+ 19 - 24
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()));