SEC-588: PersistentTokenBasedRememberMeServices implementation.

This commit is contained in:
Luke Taylor 2007-11-03 22:11:26 +00:00
parent 8b199d38ed
commit 55b1f9348d
11 changed files with 964 additions and 2 deletions

View File

@ -0,0 +1,306 @@
package org.springframework.security.ui.rememberme;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.security.Authentication;
import org.springframework.security.SpringSecurityMessageSource;
import org.springframework.security.providers.rememberme.RememberMeAuthenticationToken;
import org.springframework.security.ui.AuthenticationDetailsSource;
import org.springframework.security.ui.AuthenticationDetailsSourceImpl;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.context.support.MessageSourceAccessor;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Base class for RememberMeServices implementations.
*
* @author Luke Taylor
* @version $Id$
*/
public abstract class AbstractRememberMeServices implements RememberMeServices {
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 parameter = DEFAULT_PARAMETER;
private boolean alwaysRemember;
private String key;
private long tokenValiditySeconds = 1209600; // 14 days
/**
* Template implementation which locates the Spring Security cookie, decodes it into
* a delimited array of tokens and submits it to subclasses for processing
* via the <tt>processAutoLoginCookie</tt> method.
* <p>
* The returned username is then used to load the UserDetails object for the user, which in turn
* is used to create a valid authentication token.
*/
public final Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) {
String rememberMeCookie = extractRememberMeCookie(request);
if (rememberMeCookie == null) {
return null;
}
logger.debug("Remember-me cookie detected");
UserDetails user = null;
try {
String[] cookieTokens = decodeCookie(rememberMeCookie);
String username = processAutoLoginCookie(cookieTokens, request, response);
user = loadAndValidateUserDetails(username);
} catch (CookieTheftException cte) {
cancelCookie(request, response);
throw cte;
} catch (UsernameNotFoundException noUser) {
cancelCookie(request, response);
logger.debug("Remember-me login was valid but corresponding user not found.", noUser);
return null;
} catch (InvalidCookieException invalidCookie) {
cancelCookie(request, response);
logger.debug("Invalid remember-me cookie: " + invalidCookie.getMessage());
return null;
} catch (RememberMeAuthenticationException e) {
cancelCookie(request, response);
logger.debug("autoLogin failed", e);
return null;
}
logger.debug("Remember-me cookie accepted");
RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(key, user, user.getAuthorities());
auth.setDetails(authenticationDetailsSource.buildDetails(request));
return auth;
}
/**
* Locates the Spring Security remember me cookie in the request.
*
* @param request the submitted request which is to be authenticated
* @return the cookie value (if present), null otherwise.
*/
private String extractRememberMeCookie(HttpServletRequest request) {
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())) {
return cookies[i].getValue();
}
}
return null;
}
/**
* Decodes the cookie and splits it into a set of token strings using the ":" delimiter.
*
* @param cookieValue the value obtained from the submitted cookie
* @return the array of tokens.
* @throws InvalidCookieException if the cookie was not base64 encoded.
*/
protected String[] decodeCookie(String cookieValue) throws InvalidCookieException {
for (int j = 0; j < cookieValue.length() % 4; j++) {
cookieValue = cookieValue + "=";
}
if (!Base64.isArrayByteBase64(cookieValue.getBytes())) {
throw new InvalidCookieException( "Cookie token was not Base64 encoded; value was '" + cookieValue + "'");
}
String cookieAsPlainText = new String(Base64.decodeBase64(cookieValue.getBytes()));
return StringUtils.delimitedListToStringArray(cookieAsPlainText, DELIMITER);
}
/**
* Inverse operation of decodeCookie.
*
* @param cookieTokens the tokens to be encoded.
* @return base64 encoding of the tokens concatenated with the ":" delimiter.
*/
protected String encodeCookie(String[] cookieTokens) {
StringBuffer sb = new StringBuffer();
for(int i=0; i < cookieTokens.length; i++) {
sb.append(cookieTokens[i]);
if (i < cookieTokens.length - 1) {
sb.append(DELIMITER);
}
}
String value = sb.toString();
sb = new StringBuffer(new String(Base64.encodeBase64(value.getBytes())));
while (sb.charAt(sb.length() - 1) == '=') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
protected UserDetails loadAndValidateUserDetails(String username) 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) {
cancelCookie(request, response);
onLoginFail(request, response);
}
protected void onLoginFail(HttpServletRequest request, HttpServletResponse response) {}
/**
* Examines the incoming request and checks for the presence of the configured "remember me" parameter.
* If it's present, or if <tt>alwaysRemember</tt> is set to true, calls <tt>onLoginSucces</tt>.
*/
public final void loginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication) {
if (!rememberMeRequested(request, parameter)) {
return;
}
onLoginSuccess(request, response, successfulAuthentication);
}
/**
* Called from loginSuccess when a remember-me login has been requested.
* Typically implemented by subclasses to set a remember-me cookie and potentially store a record
* of it if the implementation requires this.
*/
protected abstract void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication successfulAuthentication);
/**
* Allows customization of whether a remember-me login has been requested.
* The default is to return true if <tt>alwaysRemember</tt> is set or the configured parameter name has
* been included in the request and is set to the value "true".
*
* @param request the request which may include
* @param parameter the configured remember-me parameter name.
*
* @return true if the request includes information indicating that a persistent login has been
* requested.
*/
protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
if (alwaysRemember) {
return true;
}
if (!ServletRequestUtils.getBooleanParameter(request, parameter, false)) {
if (logger.isDebugEnabled()) {
logger.debug("Did not send remember-me cookie (principal did not set parameter '" + parameter + "')");
}
return false;
}
return true;
}
/**
* Called from autoLogin to process the submitted pesistent login cookie. Subclasses should
* validate the cookie and perform any additional management required.
*
* @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.
* @throws RememberMeAuthenticationException if the cookie is invalid or the login is invalid for some
* other reason.
*/
protected abstract String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
HttpServletResponse response) throws RememberMeAuthenticationException;
protected void cancelCookie(HttpServletRequest request, HttpServletResponse response) {
logger.debug("Cancelling cookie");
response.addCookie(makeCancelCookie(request));
}
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 value, HttpServletRequest request, long maxAge) {
Cookie cookie = new Cookie(cookieName, value);
cookie.setMaxAge(new Long(maxAge).intValue());
cookie.setPath(StringUtils.hasLength(request.getContextPath()) ? request.getContextPath() : "/");
return cookie;
}
public void setCookieName(String cookieName) {
this.cookieName = cookieName;
}
public void setAlwaysRemember(boolean alwaysRemember) {
this.alwaysRemember = alwaysRemember;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
protected UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public void setKey(String key) {
this.key = key;
}
public void setTokenValiditySeconds(long tokenValiditySeconds) {
this.tokenValiditySeconds = tokenValiditySeconds;
}
public long getTokenValiditySeconds() {
return tokenValiditySeconds;
}
public AuthenticationDetailsSource getAuthenticationDetailsSource() {
return authenticationDetailsSource;
}
}

View File

@ -0,0 +1,11 @@
package org.springframework.security.ui.rememberme;
/**
* @author Luke Taylor
* @version $Id$
*/
public class CookieTheftException extends RememberMeAuthenticationException {
public CookieTheftException(String message) {
super(message);
}
}

View File

@ -0,0 +1,44 @@
package org.springframework.security.ui.rememberme;
import org.springframework.dao.DataIntegrityViolationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* @author Luke Taylor
* @version $Id$
*/
public class InMemoryTokenRepositoryImpl implements PersistentTokenRepository {
private Map seriesTokens = new HashMap();
public synchronized void saveToken(PersistentRememberMeToken token) {
PersistentRememberMeToken current = (PersistentRememberMeToken) seriesTokens.get(token.getSeries());
if (current != null && !token.getUsername().equals(current.getUsername())) {
throw new DataIntegrityViolationException("Series Id already exists with different username");
}
// Store it, overwriting the existing one.
seriesTokens.put(token.getSeries(), token);
}
public synchronized PersistentRememberMeToken getTokenForSeries(String seriesId) {
return (PersistentRememberMeToken) seriesTokens.get(seriesId);
}
public synchronized void removeAllTokens(String username) {
Iterator series = seriesTokens.keySet().iterator();
while (series.hasNext()) {
Object seriesId = series.next();
PersistentRememberMeToken token = (PersistentRememberMeToken) seriesTokens.get(seriesId);
if (username.equals(token.getUsername())) {
series.remove();
}
}
}
}

View File

@ -0,0 +1,11 @@
package org.springframework.security.ui.rememberme;
/**
* @author Luke Taylor
* @version $Id$
*/
public class InvalidCookieException extends RememberMeAuthenticationException {
public InvalidCookieException(String message) {
super(message);
}
}

View File

@ -0,0 +1,37 @@
package org.springframework.security.ui.rememberme;
import java.util.Date;
/**
* @author Luke Taylor
* @version $Id$
*/
public class PersistentRememberMeToken {
private String username;
private String series;
private String tokenValue;
private Date date;
public PersistentRememberMeToken(String username, String series, String tokenValue, Date date) {
this.username = username;
this.series = series;
this.tokenValue = tokenValue;
this.date = date;
}
public String getUsername() {
return username;
}
public String getSeries() {
return series;
}
public String getTokenValue() {
return tokenValue;
}
public Date getDate() {
return date;
}
}

View File

@ -0,0 +1,149 @@
package org.springframework.security.ui.rememberme;
import org.apache.commons.codec.binary.Base64;
import org.springframework.security.Authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Date;
/**
* {@link RememberMeServices} implementation based on Barry Jaspan's
* <a href="http://jaspan.com/improved_persistent_login_cookie_best_practice">Improved Persistent Login Cookie
* Best Practice</a>.
*
* There is a slight modification to the described approach, in that the username is not stored as part of the cookie
* but obtained from the persistent store via an implementation of {@link PersistentTokenRepository}. The latter
* should place a unique constraint on the series identifier, so that it is impossible for the same identifier to be
* allocated to two different users.
*
* <p>User management such as changing passwords, removing users and setting user status should be combined
* with maintenance of the user's persistent tokens.
* </p>
*
* <p>Note that while this class will use the date a token was created to check whether a presented cookie
* is older than the configured <tt>tokenValiditySeconds</tt> property and deny authentication in this case,
* it will to delete such tokens from the storage. A suitable batch process should be run periodically to
* remove expired tokens from the database.
* </p>
*
* @author Luke Taylor
* @version $Id$
*/
public class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices {
private PersistentTokenRepository tokenRepository = new InMemoryTokenRepositoryImpl();
private SecureRandom random;
public static final int DEFAULT_SERIES_LENGTH = 16;
public static final int DEFAULT_TOKEN_LENGTH = 16;
private int seriesLength = DEFAULT_SERIES_LENGTH;
private int tokenLength = DEFAULT_TOKEN_LENGTH;
public PersistentTokenBasedRememberMeServices() throws Exception {
random = SecureRandom.getInstance("SHA1PRNG");
}
/**
* Locates the presented cookie data in the token repository, using the series id.
* If the data compares successfully with that in the persistent store, a new token is generated and stored with
* the same series. The corresponding cookie value is set on the response.
*
* @param cookieTokens the series and token values
*
* @throws RememberMeAuthenticationException if there is no stored token corresponding to the submitted cookie, or
* if the token in the persistent store has expired.
* @throws InvalidCookieException if the cookie doesn't have two tokens as expected.
* @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) {
if (cookieTokens.length != 2) {
throw new InvalidCookieException("Cookie token did not contain " + 2 +
" tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
}
final String presentedSeries = cookieTokens[0];
final String presentedToken = cookieTokens[1];
PersistentRememberMeToken token = tokenRepository.getTokenForSeries(presentedSeries);
if (token == null) {
// No series match, so we can't authenticate using this cookie
throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
}
// We have a match for this user/series combination
if (!presentedToken.equals(token.getTokenValue())) {
// Token doesn't match series value. Delete all logins for this user and throw an exception to warn them.
tokenRepository.removeAllTokens(token.getUsername());
throw new CookieTheftException(messages.getMessage("PersistentTokenBasedRememberMeServices.cookieStolen",
"Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
}
if (token.getDate().getTime() + getTokenValiditySeconds()*1000 < System.currentTimeMillis()) {
throw new RememberMeAuthenticationException("Remember-me login has expired");
}
// Token also matches, so login is valid. create and save new token with the *same* series number.
PersistentRememberMeToken newToken = createNewToken(token.getUsername(), token.getSeries());
addCookie(newToken, request, response);
return token.getUsername();
}
/**
* Creates a new persistent login token with a new series number, stores the data in the
* persistent token repository and adds the corresponding cookie to the response.
*
*/
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
PersistentRememberMeToken token = createNewToken(successfulAuthentication.getName(), null);
addCookie(token, request, response);
}
private PersistentRememberMeToken createNewToken(String username, String series) {
logger.debug("Creating new persistent login token for user " + username);
if (series == null) {
byte[] newSeries = new byte[seriesLength];
random.nextBytes(newSeries);
series = new String(Base64.encodeBase64(newSeries));
logger.debug("New series: " + series);
}
byte[] token = new byte[tokenLength];
random.nextBytes(token);
PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(username, series,
new String(Base64.encodeBase64(token)), new Date());
tokenRepository.saveToken(persistentToken);
return persistentToken;
}
private void addCookie(PersistentRememberMeToken token, HttpServletRequest request, HttpServletResponse response) {
String cookieValue = encodeCookie(new String[] {token.getSeries(), token.getTokenValue()});
long maxAge = System.currentTimeMillis() + getTokenValiditySeconds() * 1000;
response.addCookie(makeValidCookie(cookieValue, request, maxAge));
}
public void setTokenRepository(PersistentTokenRepository tokenRepository) {
this.tokenRepository = tokenRepository;
}
public void setSeriesLength(int seriesLength) {
this.seriesLength = seriesLength;
}
public void setTokenLength(int tokenLength) {
this.tokenLength = tokenLength;
}
}

View File

@ -0,0 +1,15 @@
package org.springframework.security.ui.rememberme;
/**
* @author Luke Taylor
* @version $Id$
*/
public interface PersistentTokenRepository {
void saveToken(PersistentRememberMeToken token);
PersistentRememberMeToken getTokenForSeries(String seriesId);
void removeAllTokens(String username);
}

View File

@ -0,0 +1,14 @@
package org.springframework.security.ui.rememberme;
import org.springframework.security.AuthenticationException;
/**
* @author Luke Taylor
* @version $Id$
*/
public class RememberMeAuthenticationException extends AuthenticationException {
public RememberMeAuthenticationException(String message) {
super(message);
}
}

View File

@ -95,8 +95,7 @@ import org.springframework.web.bind.RequestUtils;
* </p>
*
* @author Ben Alex
* @version $Id: TokenBasedRememberMeServices.java 1871 2007-05-25 03:12:49Z
* benalex $
* @version $Id$
*/
public class TokenBasedRememberMeServices implements RememberMeServices, InitializingBean, LogoutHandler {
//~ Static fields/initializers =====================================================================================

View File

@ -0,0 +1,277 @@
package org.springframework.security.ui.rememberme;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.providers.UsernamePasswordAuthenticationToken;
import org.springframework.security.userdetails.User;
import org.springframework.security.userdetails.UserDetails;
import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;
import static org.junit.Assert.*;
import org.junit.Test;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Luke Taylor
* @version $Id$
*/
public class AbstractRememberMeServicesTests {
User joe = new User("joe", "password", true, true,true,true, new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
@Test(expected = InvalidCookieException.class)
public void nonBase64CookieShouldBeDetected() {
new MockRememberMeServices().decodeCookie("nonBase64CookieValue%");
}
@Test
public void cookieShouldBeCorrectlyEncodedAndDecoded() {
String[] cookie = new String[] {"the", "cookie", "tokens", "blah"};
MockRememberMeServices services = new MockRememberMeServices();
String encoded = services.encodeCookie(cookie);
// '=' aren't alowed in version 0 cookies.
assertFalse(encoded.endsWith("="));
String[] decoded = services.decodeCookie(encoded);
assertEquals(4, decoded.length);
assertEquals("the", decoded[0]);
assertEquals("cookie", decoded[1]);
assertEquals("tokens", decoded[2]);
assertEquals("blah", decoded[3]);
}
@Test
public void autoLoginShouldReturnNullIfNoLoginCookieIsPresented() {
MockRememberMeServices services = new MockRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
assertNull(services.autoLogin(request, response));
// shouldn't try to invalidate our cookie
assertNull(response.getCookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_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));
}
@Test
public void successfulAutoLoginReturnsExpectedAuthentication() {
MockRememberMeServices services = new MockRememberMeServices();
services.setUserDetailsService(new MockAuthenticationDao(joe, false));
assertNotNull(services.getUserDetailsService());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(createLoginCookie("cookie:1:2"));
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNotNull(result);
}
@Test
public void autoLoginShouldFailIfInvalidCookieExceptionIsRaised() {
MockRememberMeServices services = new MockRememberMeServices();
services.setUserDetailsService(new MockAuthenticationDao(joe, true));
MockHttpServletRequest request = new MockHttpServletRequest();
// Wrong number of tokes
request.setCookies(createLoginCookie("cookie:1"));
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
assertCookieCancelled(response);
}
@Test
public void autoLoginShouldFailIfUserNotFound() {
MockRememberMeServices services = new MockRememberMeServices();
services.setUserDetailsService(new MockAuthenticationDao(joe, true));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(createLoginCookie("cookie:1:2"));
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
assertCookieCancelled(response);
}
@Test
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));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(createLoginCookie("cookie:1:2"));
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
assertCookieCancelled(response);
}
@Test
public void loginFailShouldCancelCookie() {
MockRememberMeServices services = new MockRememberMeServices();
services.setUserDetailsService(new MockAuthenticationDao(joe, true));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContextPath("contextpath");
request.setCookies(createLoginCookie("cookie:1:2"));
MockHttpServletResponse response = new MockHttpServletResponse();
services.loginFail(request, response);
assertCookieCancelled(response);
}
@Test(expected = CookieTheftException.class)
public void cookieTheftExceptionShouldBeRethrown() {
MockRememberMeServices services = new MockRememberMeServices() {
protected String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) {
throw new CookieTheftException("Pretending cookie was stolen");
}
};
services.setUserDetailsService(new MockAuthenticationDao(joe, false));
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(createLoginCookie("cookie:1:2"));
MockHttpServletResponse response = new MockHttpServletResponse();
services.autoLogin(request, response);
}
@Test
public void loginSuccessCallsOnLoginSuccessCorrectly() {
MockRememberMeServices services = new MockRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication auth = new UsernamePasswordAuthenticationToken("joe","password");
// No parameter set
services = new MockRememberMeServices();
services.loginSuccess(request, response, auth);
assertFalse(services.loginSuccessCalled);
// Parameter set to true
services = new MockRememberMeServices();
request.setParameter(MockRememberMeServices.DEFAULT_PARAMETER, "true");
services.loginSuccess(request, response, auth);
assertTrue(services.loginSuccessCalled);
// Different parameter name, set to true
services = new MockRememberMeServices();
services.setParameter("my_parameter");
request.setParameter("my_parameter", "true");
services.loginSuccess(request, response, auth);
assertTrue(services.loginSuccessCalled);
// Parameter set to false
services = new MockRememberMeServices();
request.setParameter(MockRememberMeServices.DEFAULT_PARAMETER, "false");
services.loginSuccess(request, response, auth);
assertFalse(services.loginSuccessCalled);
// alwaysRemember set to true
services = new MockRememberMeServices();
services.setAlwaysRemember(true);
services.loginSuccess(request, response, auth);
assertTrue(services.loginSuccessCalled);
}
@Test
public void makeValidCookieUsesCorrectNamePathAndValue() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setContextPath("contextpath");
MockRememberMeServices services = new MockRememberMeServices();
services.setCookieName("mycookiename");
Cookie cookie = services.makeValidCookie("mycookie", request, 1000);
assertTrue(cookie.getValue().equals("mycookie"));
assertTrue(cookie.getName().equals("mycookiename"));
assertTrue(cookie.getPath().equals("contextpath"));
}
private Cookie[] createLoginCookie(String cookieToken) {
MockRememberMeServices services = new MockRememberMeServices();
Cookie cookie = new Cookie(AbstractRememberMeServices.SPRING_SECURITY_PERSISTENT_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);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
//~ Inner Classes ==================================================================================================
private class MockRememberMeServices extends AbstractRememberMeServices {
boolean loginSuccessCalled;
private MockRememberMeServices() {
setKey("key");
}
protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) {
loginSuccessCalled = true;
}
protected String processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) throws RememberMeAuthenticationException {
if(cookieTokens.length != 3) {
throw new InvalidCookieException("deliberate exception");
}
return "joe";
}
}
private class MockAuthenticationDao implements UserDetailsService {
private UserDetails toReturn;
private boolean throwException;
public MockAuthenticationDao(UserDetails toReturn, boolean throwException) {
this.toReturn = toReturn;
this.throwException = throwException;
}
public UserDetails loadUserByUsername(String username) {
if (throwException) {
throw new UsernameNotFoundException("as requested by mock");
}
return toReturn;
}
}
}

View File

@ -0,0 +1,99 @@
package org.springframework.security.ui.rememberme;
import static org.junit.Assert.assertEquals;
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;
/**
* @author Luke Taylor
* @version $Id$
*/
public class PersistentTokenBasedRememberMeServicesTests {
private PersistentTokenBasedRememberMeServices services;
@Before
public void setUpData() throws Exception {
services = new PersistentTokenBasedRememberMeServices();
}
@Test(expected = InvalidCookieException.class)
public void loginIsRejectedWithWrongNumberOfCookieTokens() {
services.setCookieName("mycookiename");
services.processAutoLoginCookie(new String[] {"series", "token", "extra"}, new MockHttpServletRequest(),
new MockHttpServletResponse());
}
@Test(expected = RememberMeAuthenticationException.class)
public void loginIsRejectedWhenNoTokenMatchingSeriesIsFound() {
services.setCookieName("mycookiename");
services.setTokenRepository(new MockTokenRepository(null));
services.processAutoLoginCookie(new String[] {"series", "token"}, new MockHttpServletRequest(),
new MockHttpServletResponse());
}
@Test(expected = CookieTheftException.class)
public void cookieTheftIsDetectedWhenSeriesAndTokenDontMatch() {
services.setCookieName("mycookiename");
PersistentRememberMeToken token = new PersistentRememberMeToken("joe", "series","wrongtoken", new Date());
services.setTokenRepository(new MockTokenRepository(token));
services.processAutoLoginCookie(new String[] {"series", "token"}, new MockHttpServletRequest(),
new MockHttpServletResponse());
}
@Test
public void successfulAutoLoginCreatesNewTokenAndCookieWithSameSeries() {
services.setCookieName("mycookiename");
MockTokenRepository repo =
new MockTokenRepository(new PersistentRememberMeToken("joe", "series","token", new Date()));
services.setTokenRepository(repo);
// 12 => b64 length will be 16
services.setTokenLength(12);
services.processAutoLoginCookie(new String[] {"series", "token"}, new MockHttpServletRequest(),
new MockHttpServletResponse());
assertEquals("series",repo.getStoredToken().getSeries());
assertEquals(16, repo.getStoredToken().getTokenValue().length());
}
@Test
public void loginSuccessCreatesNewTokenAndCookieWithNewSeries() {
services.setAlwaysRemember(true);
MockTokenRepository repo = new MockTokenRepository(null);
services.setTokenRepository(repo);
services.setTokenLength(12);
services.setSeriesLength(12);
services.loginSuccess(new MockHttpServletRequest(),
new MockHttpServletResponse(), new UsernamePasswordAuthenticationToken("joe","password"));
assertEquals(16, repo.getStoredToken().getSeries().length());
assertEquals(16, repo.getStoredToken().getTokenValue().length());
}
private class MockTokenRepository implements PersistentTokenRepository {
private PersistentRememberMeToken storedToken;
private MockTokenRepository(PersistentRememberMeToken token) {
storedToken = token;
}
public void saveToken(PersistentRememberMeToken token) {
storedToken = token;
}
public PersistentRememberMeToken getTokenForSeries(String seriesId) {
return storedToken;
}
public void removeAllTokens(String username) {
}
PersistentRememberMeToken getStoredToken() {
return storedToken;
}
}
}