1
0
mirror of synced 2026-08-05 09:47:05 +00:00

Added remember-me services.

This commit is contained in:
Ben Alex
2005-03-01 02:30:38 +00:00
parent 0d33b06990
commit f1e071b0f1
28 changed files with 2098 additions and 21 deletions
@@ -16,6 +16,7 @@
package net.sf.acegisecurity;
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
/**
@@ -39,7 +40,7 @@ public class AuthenticationTrustResolverImpl
//~ Instance fields ========================================================
private Class anonymousClass = AnonymousAuthenticationToken.class;
private Class rememberMeClass;
private Class rememberMeClass = RememberMeAuthenticationToken.class;
//~ Methods ================================================================
@@ -0,0 +1,86 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.rememberme;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation that validates {@link
* net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken}s.
*
* <p>
* To be successfully validated, the {@link{@link
* net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken#getKeyHash()}
* must match this class' {@link #getKey()}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeAuthenticationProvider implements AuthenticationProvider,
InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(RememberMeAuthenticationProvider.class);
//~ Instance fields ========================================================
private String key;
//~ Methods ================================================================
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(key);
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication)
.getKeyHash()) {
throw new BadCredentialsException(
"The presented RememberMeAuthenticationToken does not contain the expected key");
}
return authentication;
}
public boolean supports(Class authentication) {
return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication));
}
}
@@ -0,0 +1,139 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.rememberme;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.providers.AbstractAuthenticationToken;
import java.io.Serializable;
/**
* Represents a remembered <code>Authentication</code>.
*
* <p>
* A remembered <code>Authentication</code> must provide a fully valid
* <code>Authentication</code>, including the <code>GrantedAuthority</code>[]s
* that apply.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeAuthenticationToken extends AbstractAuthenticationToken
implements Serializable {
//~ Instance fields ========================================================
private Object principal;
private GrantedAuthority[] authorities;
private int keyHash;
//~ Constructors ===========================================================
/**
* Constructor.
*
* @param key to identify if this object made by an authorised client
* @param principal the principal (typically a <code>UserDetails</code>)
* @param authorities the authorities granted to the principal
*
* @throws IllegalArgumentException if a <code>null</code> was passed
*/
public RememberMeAuthenticationToken(String key, Object principal,
GrantedAuthority[] authorities) {
if ((key == null) || ("".equals(key)) || (principal == null)
|| "".equals(principal) || (authorities == null)
|| (authorities.length == 0)) {
throw new IllegalArgumentException(
"Cannot pass null or empty values to constructor");
}
for (int i = 0; i < authorities.length; i++) {
if (authorities[i] == null) {
throw new IllegalArgumentException("Granted authority element "
+ i
+ " is null - GrantedAuthority[] cannot contain any null elements");
}
}
this.keyHash = key.hashCode();
this.principal = principal;
this.authorities = authorities;
}
protected RememberMeAuthenticationToken() {
throw new IllegalArgumentException("Cannot use default constructor");
}
//~ Methods ================================================================
/**
* Ignored (always <code>true</code>).
*
* @param isAuthenticated ignored
*/
public void setAuthenticated(boolean isAuthenticated) {
// ignored
}
/**
* Always returns <code>true</code>.
*
* @return true
*/
public boolean isAuthenticated() {
return true;
}
public GrantedAuthority[] getAuthorities() {
return this.authorities;
}
/**
* Always returns an empty <code>String</code>
*
* @return an empty String
*/
public Object getCredentials() {
return "";
}
public int getKeyHash() {
return this.keyHash;
}
public Object getPrincipal() {
return this.principal;
}
public boolean equals(Object obj) {
if (!super.equals(obj)) {
return false;
}
if (obj instanceof RememberMeAuthenticationToken) {
RememberMeAuthenticationToken test = (RememberMeAuthenticationToken) obj;
if (this.getKeyHash() != test.getKeyHash()) {
return false;
}
return true;
}
return false;
}
}
@@ -0,0 +1,5 @@
<html>
<body>
Authentication provider that processes <code>RememberMeAuthenticationToken</code>s.
</body>
</html>
@@ -26,12 +26,16 @@ import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.providers.cas.ProxyUntrustedException;
import net.sf.acegisecurity.ui.rememberme.NullRememberMeServices;
import net.sf.acegisecurity.ui.rememberme.RememberMeServices;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import javax.servlet.Filter;
@@ -106,6 +110,7 @@ public abstract class AbstractProcessingFilter implements Filter,
//~ Instance fields ========================================================
private AuthenticationManager authenticationManager;
private RememberMeServices rememberMeServices = new NullRememberMeServices();
/**
* Where to redirect the browser if authentication fails due to incorrect
@@ -194,6 +199,14 @@ public abstract class AbstractProcessingFilter implements Filter,
*/
public abstract String getDefaultFilterProcessesUrl();
public void setRememberMeServices(RememberMeServices rememberMeServices) {
this.rememberMeServices = rememberMeServices;
}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
/**
* Performs actual authentication.
*
@@ -306,6 +319,8 @@ public abstract class AbstractProcessingFilter implements Filter,
throw new IllegalArgumentException(
"authenticationManager must be specified");
}
Assert.notNull(this.rememberMeServices);
}
/**
@@ -370,7 +385,8 @@ public abstract class AbstractProcessingFilter implements Filter,
HttpServletResponse response) throws IOException {}
protected void onSuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException {}
HttpServletResponse response, Authentication authResult)
throws IOException {}
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response) throws IOException {}
@@ -429,7 +445,9 @@ public abstract class AbstractProcessingFilter implements Filter,
+ targetUrl);
}
onSuccessfulAuthentication(request, response);
onSuccessfulAuthentication(request, response, authResult);
rememberMeServices.loginSuccess(request, response, authResult);
response.sendRedirect(response.encodeRedirectURL(targetUrl));
}
@@ -481,6 +499,8 @@ public abstract class AbstractProcessingFilter implements Filter,
onUnsuccessfulAuthentication(request, response);
rememberMeServices.loginFail(request, response);
response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
+ failureUrl));
}
@@ -0,0 +1,47 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import net.sf.acegisecurity.Authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Implementation of {@link NullRememberMeServices} that does nothing.
*
* <p>
* Used as a default by several framework classes.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class NullRememberMeServices implements RememberMeServices {
//~ Methods ================================================================
public Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
return null;
}
public void loginFail(HttpServletRequest request,
HttpServletResponse response) {}
public void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {}
}
@@ -0,0 +1,131 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Detects if there is no <code>Authentication</code> object in the
* <code>ContextHolder</code>, and populates it with a remember-me
* authentication token if a {@link
* net.sf.acegisecurity.ui.rememberme.RememberMeServices} implementation so
* requests.
*
* <p>
* Concrete <code>RememberMeServices</code> implementations will have their
* {@link
* net.sf.acegisecurity.ui.rememberme.RememberMeServices#autoLogin(HttpServletRequest,
* HttpServletResponse)} method called by this filter. The
* <code>Authentication</code> or <code>null</code> returned by that method
* will be placed into the <code>ContextHolder</code>.
* </p>
*
* <P>
* <B>Do not use this class directly.</B> Instead configure
* <code>web.xml</code> to use the {@link
* net.sf.acegisecurity.util.FilterToBeanProxy}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeProcessingFilter implements Filter, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(RememberMeProcessingFilter.class);
//~ Instance fields ========================================================
private RememberMeServices rememberMeServices = new NullRememberMeServices();
//~ Methods ================================================================
public void afterPropertiesSet() throws Exception {
Assert.notNull(rememberMeServices);
}
/**
* Does nothing - we reply on IoC lifecycle services instead.
*/
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("Can only process HttpServletRequest");
}
if (!(response instanceof HttpServletResponse)) {
throw new ServletException("Can only process HttpServletResponse");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
SecureContext sc = SecureContextUtils.getSecureContext();
if (sc.getAuthentication() == null) {
sc.setAuthentication(rememberMeServices.autoLogin(httpRequest,
httpResponse));
if (logger.isDebugEnabled()) {
logger.debug("Replaced ContextHolder with remember-me token: '"
+ sc.getAuthentication() + "'");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug(
"ContextHolder not replaced with remember-me token, as ContextHolder already contained: '"
+ sc.getAuthentication() + "'");
}
}
chain.doFilter(request, response);
}
/**
* Does nothing - we reply on IoC lifecycle services instead.
*
* @param arg0 not used
*
* @throws ServletException not thrown
*/
public void init(FilterConfig arg0) throws ServletException {}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
this.rememberMeServices = rememberMeServices;
}
}
@@ -0,0 +1,115 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import net.sf.acegisecurity.Authentication;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Implement by a class that is capable of providing a remember-me service.
*
* <P>
* Acegi Security filters (namely {@link
* net.sf.acegisecurity.ui.AbstractProcessingFilter} and {@link
* net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter} will call
* the methods provided by an implementation of this interface.
* </p>
*
* <P>
* Implementations may implement any type of remember-me capability they wish.
* Rolling cookies (as per <a
* href="http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice">http://fishbowl.pastiche.org/2004/01/19/persistent_login_cookie_best_practice</a>)
* can be used, as can simple implementations that don't require a persistent
* store. Implementations also determine the validity period of a remember-me
* cookie. This interface has been designed to accommodate any of these
* remember-me models.
* </p>
*
* <p>
* This interface does not define how remember-me services should offer a
* "cancel all remember-me tokens" type capability, as this will be
* implementation specific and requires no hooks into Acegi Security.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public interface RememberMeServices {
//~ Methods ================================================================
/**
* This method will be called whenever the <code>ContextHolder</code> does
* not contain an <code>Authentication</code> and the Acegi Security
* system wishes to provide an implementation with an opportunity to
* authenticate the request using remember-me capabilities. Acegi Security
* makes no attempt whatsoever to determine whether the browser has
* requested remember-me services or presented a vaild cookie. Such
* determinations are left to the implementation. If a browser has
* presented an unauthorised cookie for whatever reason, it should be
* silently ignored and invalidated using the
* <code>HttpServletResponse</code> object.
*
* <p>
* The returned <code>Authentication</code> must be acceptable to {@link
* net.sf.acegisecurity.AuthenticationManager} or {@link
* net.sf.acegisecurity.providers.AuthenticationProvider} defined by the
* web application. It is recommended {@link
* net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken}
* be used in most cases, as it has a corresponding authentication
* provider.
* </p>
*
* @param request to look for a remember-me token within
* @param response to change, cancel or modify the remember-me token
*
* @return a valid authentication object, or <code>null</code> if the
* request should not be authenticated
*/
public Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response);
/**
* Called whenever an interactive authentication attempt was made, but the
* credentials supplied by the user were missing or otherwise invalid.
* Implementations should invalidate any and all remember-me tokens
* indicated in the <code>HttpServletRequest</code>.
*
* @param request that contained an invalid authentication request
* @param response to change, cancel or modify the remember-me token
*/
public void loginFail(HttpServletRequest request,
HttpServletResponse response);
/**
* Called whenever an interactive authentication attempt is successful. An
* implementation may automatically set a remember-me token in the
* <code>HttpServletResponse</code>, although this is not recommended.
* Instead, implementations should typically look for a request parameter
* that indicates the browser has presented an explicit request for
* authentication to be remembered, such as the presence of a HTTP POST
* parameter.
*
* @param request that contained the valid authentication request
* @param response to change, cancel or modify the remember-me token
* @param successfulAuthentication representing the successfully
* authenticated principal
*/
public void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication);
}
@@ -0,0 +1,352 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.dao.AuthenticationDao;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
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.Date;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Identifies previously remembered users by a Base-64 encoded cookie.
*
* <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)}. 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 credentials - not the time
* period they last logged in via remember-me. The implementation will only
* send a remember-me token if the parameter defined by {@link
* #setParameter(String)} is present.
* </p>
*
* <p>
* An {@link net.sf.acegisecurity.providers.dao.AuthenticationDao} is required
* by this implementation, so that it can construct a valid
* <code>Authentication</code> from the returned {@link
* net.sf.acegisecurity.UserDetails}. This is also necessary so that the
* user's password is available and can be checked as part of the encoded
* cookie.
* </p>
*
* <p>
* The cookie encoded by this implementation adopts the following form:
* </p>
*
* <p>
* <code> username + ":" + expiryTime + ":" + Md5Hex(username + ":" +
* expiryTime + ":" + password + ":" + key) </code>.
* </p>
*
* <p>
* 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 machine (eg kiosk system, Internet cafe etc). Most importantly, at
* no time is the user's password ever sent to the user agent, providing an
* important security safeguard. Unfortunately the username is necessary in
* this implementation (as we do not want to rely on a database for
* remember-me services) and as such high security applications should be
* aware of this occasionally undesired disclosure of a valid username.
* </p>
*
* <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.
* </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(int)}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class TokenBasedRememberMeServices implements RememberMeServices,
InitializingBean {
//~ Static fields/initializers =============================================
public static final String ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = "ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE";
public static final String DEFAULT_PARAMETER = "_acegi_security_remember_me";
protected static final Log logger = LogFactory.getLog(TokenBasedRememberMeServices.class);
//~ Instance fields ========================================================
private AuthenticationDao authenticationDao;
private String key;
private String parameter = DEFAULT_PARAMETER;
private int tokenValiditySeconds = 1209600; // 14 days
//~ Methods ================================================================
public void setAuthenticationDao(AuthenticationDao authenticationDao) {
this.authenticationDao = authenticationDao;
}
public AuthenticationDao getAuthenticationDao() {
return authenticationDao;
}
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setParameter(String parameter) {
this.parameter = parameter;
}
public String getParameter() {
return parameter;
}
public void setTokenValiditySeconds(int tokenValiditySeconds) {
this.tokenValiditySeconds = tokenValiditySeconds;
}
public int getTokenValiditySeconds() {
return tokenValiditySeconds;
}
public void afterPropertiesSet() throws Exception {
Assert.hasLength(key);
Assert.hasLength(parameter);
Assert.notNull(authenticationDao);
}
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 (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY.equals(
cookies[i].getName())) {
String cookieValue = cookies[i].getValue();
if (Base64.isArrayByteBase64(cookieValue.getBytes())) {
if (logger.isDebugEnabled()) {
logger.debug("Remember-me cookie detected");
}
// 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) {
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;
}
// Check it has not expired
if (tokenExpiryTime < System.currentTimeMillis()) {
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;
try {
userDetails = this.authenticationDao
.loadUserByUsername(cookieTokens[0]);
} catch (UsernameNotFoundException notFound) {
cancelCookie(request, response,
"Cookie token[0] contained username '"
+ cookieTokens[0] + "' but was not found");
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 ContextHolder population, whilst
// if invalid, will cause the cookie to be cancelled)
String expectedTokenSignature = DigestUtils.md5Hex(userDetails
.getUsername() + ":" + tokenExpiryTime + ":"
+ userDetails.getPassword() + ":" + this.key);
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");
}
return new RememberMeAuthenticationToken(this.key,
userDetails, userDetails.getAuthorities());
} else {
cancelCookie(request, response,
"Cookie token did not contain 3 tokens; decoded value was '"
+ cookieAsPlainText + "'");
return null;
}
} else {
cancelCookie(request, response,
"Cookie token was not Base64 encoded; value was '"
+ cookieValue + "'");
return null;
}
}
}
return null;
}
public void loginFail(HttpServletRequest request,
HttpServletResponse response) {
cancelCookie(request, response,
"Interactive authentication attempt was unsuccessful");
}
public void loginSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication successfulAuthentication) {
// Exit if the principal hasn't asked to be remembered
if (!RequestUtils.getBooleanParameter(request, parameter, false)) {
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;
String password;
if (successfulAuthentication.getPrincipal() instanceof UserDetails) {
username = ((UserDetails) successfulAuthentication.getPrincipal())
.getUsername();
password = ((UserDetails) successfulAuthentication.getPrincipal())
.getPassword();
} else {
username = successfulAuthentication.getPrincipal().toString();
password = successfulAuthentication.getCredentials().toString();
}
Assert.hasLength(username);
Assert.hasLength(password);
long expiryTime = System.currentTimeMillis()
+ (tokenValiditySeconds * 1000);
// construct token to put in cookie; format is:
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
String signatureValue = new String(DigestUtils.md5Hex(username + ":"
+ expiryTime + ":" + password + ":" + key));
String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
String tokenValueBase64 = new String(Base64.encodeBase64(
tokenValue.getBytes()));
response.addCookie(makeValidCookie(expiryTime, tokenValueBase64));
if (logger.isDebugEnabled()) {
logger.debug("Added remember-me cookie for user '" + username
+ "', expiry: '" + new Date(expiryTime) + "'");
}
}
protected Cookie makeCancelCookie() {
Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
null);
cookie.setMaxAge(0);
return cookie;
}
protected Cookie makeValidCookie(long expiryTime, String tokenValueBase64) {
Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
tokenValueBase64);
cookie.setMaxAge(60 * 60 * 24 * 365 * 5); // 5 years
return cookie;
}
private void cancelCookie(HttpServletRequest request,
HttpServletResponse response, String reasonForLog) {
if ((reasonForLog != null) && logger.isDebugEnabled()) {
logger.debug("Cancelling cookie for reason: " + reasonForLog);
}
response.addCookie(makeCancelCookie());
}
}
@@ -0,0 +1,5 @@
<html>
<body>
Support for remembering a user between different web sessions.
</body>
</html>
@@ -19,6 +19,7 @@ import junit.framework.TestCase;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
/**
@@ -54,6 +55,16 @@ public class AuthenticationTrustResolverImplTests extends TestCase {
new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")})));
}
public void testCorrectOperationIsRememberMe() {
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
assertTrue(trustResolver.isRememberMe(
new RememberMeAuthenticationToken("ignored", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")})));
assertFalse(trustResolver.isAnonymous(
new TestingAuthenticationToken("ignored", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")})));
}
public void testGettersSetters() {
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
@@ -62,6 +73,9 @@ public class AuthenticationTrustResolverImplTests extends TestCase {
trustResolver.setAnonymousClass(String.class);
assertEquals(String.class, trustResolver.getAnonymousClass());
assertNull(trustResolver.getRememberMeClass());
assertEquals(RememberMeAuthenticationToken.class,
trustResolver.getRememberMeClass());
trustResolver.setRememberMeClass(String.class);
assertEquals(String.class, trustResolver.getRememberMeClass());
}
}
@@ -54,6 +54,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
private Map attribMap = new HashMap();
private Map headersMap = new HashMap();
private Map paramMap = new HashMap();
private Map cookiesMap = new HashMap();
private Principal principal;
private String contextPath = "";
private String pathInfo; // null for no extra path
@@ -75,6 +76,15 @@ public class MockHttpServletRequest implements HttpServletRequest {
this.queryString = queryString;
}
public MockHttpServletRequest(Map headers, HttpSession session, String queryString, Cookie[] cookies) {
this.queryString = queryString;
this.headersMap = headers;
this.session = session;
for (int i = 0; i < cookies.length; i++) {
cookiesMap.put(cookies[i].getName(), cookies[i]);
}
}
public MockHttpServletRequest(Map headers, Principal principal,
HttpSession session) {
this.headersMap = headers;
@@ -129,7 +139,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
}
public Cookie[] getCookies() {
throw new UnsupportedOperationException("mock method not implemented");
return (Cookie[]) cookiesMap.values().toArray(new Cookie[] {});
}
public long getDateHeader(String arg0) {
@@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
public class MockHttpServletResponse implements HttpServletResponse {
//~ Instance fields ========================================================
private Map cookiesMap = new HashMap();
private Map headersMap = new HashMap();
private String errorMessage;
private String redirect;
@@ -72,6 +73,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
throw new UnsupportedOperationException("mock method not implemented");
}
public Cookie getCookieByName(String name) {
return (Cookie) cookiesMap.get(name);
}
public void setDateHeader(String arg0, long arg1) {
throw new UnsupportedOperationException("mock method not implemented");
}
@@ -131,7 +136,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
}
public void addCookie(Cookie arg0) {
throw new UnsupportedOperationException("mock method not implemented");
cookiesMap.put(arg0.getName(), arg0);
}
public void addDateHeader(String arg0, long arg1) {
@@ -0,0 +1,122 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.rememberme;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
/**
* Tests {@link RememberMeAuthenticationProvider}.
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeAuthenticationProviderTests extends TestCase {
//~ Constructors ===========================================================
public RememberMeAuthenticationProviderTests() {
super();
}
public RememberMeAuthenticationProviderTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(RememberMeAuthenticationProviderTests.class);
}
public void testDetectsAnInvalidKey() throws Exception {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
aap.setKey("qwerty");
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("WRONG_KEY",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
try {
Authentication result = aap.authenticate(token);
fail("Should have thrown BadCredentialsException");
} catch (BadCredentialsException expected) {
assertEquals("The presented RememberMeAuthenticationToken does not contain the expected key",
expected.getMessage());
}
}
public void testDetectsMissingKey() throws Exception {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
try {
aap.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testGettersSetters() throws Exception {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
aap.setKey("qwerty");
aap.afterPropertiesSet();
assertEquals("qwerty", aap.getKey());
}
public void testIgnoresClassesItDoesNotSupport() throws Exception {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
aap.setKey("qwerty");
TestingAuthenticationToken token = new TestingAuthenticationToken("user",
"password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
assertFalse(aap.supports(TestingAuthenticationToken.class));
// Try it anyway
assertNull(aap.authenticate(token));
}
public void testNormalOperation() throws Exception {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
aap.setKey("qwerty");
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("qwerty",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
Authentication result = aap.authenticate(token);
assertEquals(result, token);
}
public void testSupports() {
RememberMeAuthenticationProvider aap = new RememberMeAuthenticationProvider();
assertTrue(aap.supports(RememberMeAuthenticationToken.class));
assertFalse(aap.supports(TestingAuthenticationToken.class));
}
}
@@ -0,0 +1,190 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.rememberme;
import junit.framework.TestCase;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import java.util.List;
import java.util.Vector;
/**
* Tests {@link RememberMeAuthenticationToken}.
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeAuthenticationTokenTests extends TestCase {
//~ Constructors ===========================================================
public RememberMeAuthenticationTokenTests() {
super();
}
public RememberMeAuthenticationTokenTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(RememberMeAuthenticationTokenTests.class);
}
public void testConstructorRejectsNulls() {
try {
new RememberMeAuthenticationToken(null, "Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RememberMeAuthenticationToken("key", null,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RememberMeAuthenticationToken("key", "Test", null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RememberMeAuthenticationToken("key", "Test",
new GrantedAuthority[] {null});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new RememberMeAuthenticationToken("key", "Test",
new GrantedAuthority[] {});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testEqualsWhenEqual() {
List proxyList1 = new Vector();
proxyList1.add("https://localhost/newPortal/j_acegi_cas_security_check");
RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertEquals(token1, token2);
}
public void testGetters() {
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertEquals("key".hashCode(), token.getKeyHash());
assertEquals("Test", token.getPrincipal());
assertEquals("", token.getCredentials());
assertEquals("ROLE_ONE", token.getAuthorities()[0].getAuthority());
assertEquals("ROLE_TWO", token.getAuthorities()[1].getAuthority());
assertTrue(token.isAuthenticated());
}
public void testNoArgConstructor() {
try {
new RememberMeAuthenticationToken();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testNotEqualsDueToAbstractParentEqualsCheck() {
RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("key",
"DIFFERENT_PRINCIPAL",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertFalse(token1.equals(token2));
}
public void testNotEqualsDueToDifferentAuthenticationClass() {
RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test",
"Password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
token2.setAuthenticated(true);
assertFalse(token1.equals(token2));
}
public void testNotEqualsDueToKey() {
RememberMeAuthenticationToken token1 = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
RememberMeAuthenticationToken token2 = new RememberMeAuthenticationToken("DIFFERENT_KEY",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertFalse(token1.equals(token2));
}
public void testSetAuthenticatedIgnored() {
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertTrue(token.isAuthenticated());
token.setAuthenticated(false); // ignored
assertTrue(token.isAuthenticated());
}
}
@@ -30,6 +30,7 @@ import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContextImpl;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices;
import java.io.IOException;
@@ -150,6 +151,11 @@ public class AbstractProcessingFilterTests extends TestCase {
public void testGettersSetters() {
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
assertNotNull(filter.getRememberMeServices());
filter.setRememberMeServices(new TokenBasedRememberMeServices());
assertEquals(TokenBasedRememberMeServices.class,
filter.getRememberMeServices().getClass());
filter.setAuthenticationFailureUrl("/x");
assertEquals("/x", filter.getAuthenticationFailureUrl());
@@ -0,0 +1,51 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import junit.framework.TestCase;
/**
* Tests {@link net.sf.acegisecurity.ui.rememberme.NullRememberMeServices}.
*
* @author Ben Alex
* @version $Id$
*/
public class NullRememberMeServicesTests extends TestCase {
//~ Constructors ===========================================================
public NullRememberMeServicesTests() {
super();
}
public NullRememberMeServicesTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(NullRememberMeServicesTests.class);
}
public void testAlwaysReturnsNull() {
NullRememberMeServices services = new NullRememberMeServices();
assertNull(services.autoLogin(null,null));
services.loginFail(null,null);
services.loginSuccess(null,null,null);
assertTrue(true);
}
}
@@ -0,0 +1,223 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.MockFilterConfig;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextImpl;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
/**
* Tests {@link RememberMeProcessingFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public class RememberMeProcessingFilterTests extends TestCase {
//~ Constructors ===========================================================
public RememberMeProcessingFilterTests() {
super();
}
public RememberMeProcessingFilterTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(RememberMeProcessingFilterTests.class);
}
public void testDoFilterWithNonHttpServletRequestDetected()
throws Exception {
RememberMeProcessingFilter filter = new RememberMeProcessingFilter();
try {
filter.doFilter(null, new MockHttpServletResponse(),
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("Can only process HttpServletRequest",
expected.getMessage());
}
}
public void testDoFilterWithNonHttpServletResponseDetected()
throws Exception {
RememberMeProcessingFilter filter = new RememberMeProcessingFilter();
try {
filter.doFilter(new MockHttpServletRequest("dc"), null,
new MockFilterChain());
fail("Should have thrown ServletException");
} catch (ServletException expected) {
assertEquals("Can only process HttpServletResponse",
expected.getMessage());
}
}
public void testDetectsRememberMeServicesProperty() throws Exception {
RememberMeProcessingFilter filter = new RememberMeProcessingFilter();
// check default is NullRememberMeServices
assertEquals(NullRememberMeServices.class, filter.getRememberMeServices().getClass());
// check getter/setter
filter.setRememberMeServices(new TokenBasedRememberMeServices());
assertEquals(TokenBasedRememberMeServices.class, filter.getRememberMeServices().getClass());
// check detects if made null
filter.setRememberMeServices(null);
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testOperationWhenAuthenticationExistsInContextHolder()
throws Exception {
// Put an Authentication object into the ContextHolder
SecureContext sc = SecureContextUtils.getSecureContext();
Authentication originalAuth = new TestingAuthenticationToken("user",
"password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
sc.setAuthentication(originalAuth);
ContextHolder.setContext(sc);
// Setup our filter correctly
Authentication remembered = new TestingAuthenticationToken("remembered",
"password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_REMEMBERED")});
RememberMeProcessingFilter filter = new RememberMeProcessingFilter();
filter.setRememberMeServices(new MockRememberMeServices(remembered));
filter.afterPropertiesSet();
// Test
executeFilterInContainerSimulator(new MockFilterConfig(), filter,
new MockHttpServletRequest("x"), new MockHttpServletResponse(),
new MockFilterChain(true));
// Ensure filter didn't change our original object
assertEquals(originalAuth,
SecureContextUtils.getSecureContext().getAuthentication());
}
public void testOperationWhenNoAuthenticationInContextHolder()
throws Exception {
Authentication remembered = new TestingAuthenticationToken("remembered",
"password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_REMEMBERED")});
RememberMeProcessingFilter filter = new RememberMeProcessingFilter();
filter.setRememberMeServices(new MockRememberMeServices(remembered));
filter.afterPropertiesSet();
executeFilterInContainerSimulator(new MockFilterConfig(), filter,
new MockHttpServletRequest("x"), new MockHttpServletResponse(),
new MockFilterChain(true));
Authentication auth = SecureContextUtils.getSecureContext()
.getAuthentication();
// Ensure filter setup with our remembered authentication object
assertEquals(remembered,
SecureContextUtils.getSecureContext().getAuthentication());
}
protected void setUp() throws Exception {
super.setUp();
ContextHolder.setContext(new SecureContextImpl());
}
protected void tearDown() throws Exception {
super.tearDown();
ContextHolder.setContext(null);
}
private void executeFilterInContainerSimulator(FilterConfig filterConfig,
Filter filter, ServletRequest request, ServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
filter.destroy();
}
//~ Inner Classes ==========================================================
private class MockFilterChain implements FilterChain {
private boolean expectToProceed;
public MockFilterChain(boolean expectToProceed) {
this.expectToProceed = expectToProceed;
}
private MockFilterChain() {
super();
}
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (expectToProceed) {
assertTrue(true);
} else {
fail("Did not expect filter chain to proceed");
}
}
}
private class MockRememberMeServices implements RememberMeServices
{
private Authentication authToReturn;
public MockRememberMeServices(Authentication authToReturn) {
this.authToReturn = authToReturn;
}
public Authentication autoLogin(HttpServletRequest request,
HttpServletResponse response) {
return authToReturn;
}
public void loginFail(HttpServletRequest request,
HttpServletResponse response) {
}
public void loginSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication successfulAuthentication) {
}
}
}
@@ -0,0 +1,412 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.ui.rememberme;
import junit.framework.TestCase;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
import net.sf.acegisecurity.providers.dao.AuthenticationDao;
import net.sf.acegisecurity.providers.dao.User;
import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.util.StringUtils;
import java.util.Date;
import javax.servlet.http.Cookie;
/**
* Tests {@link
* net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices}.
*
* @author Ben Alex
* @version $Id$
*/
public class TokenBasedRememberMeServicesTests extends TestCase {
//~ Constructors ===========================================================
public TokenBasedRememberMeServicesTests() {
super();
}
public TokenBasedRememberMeServicesTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(TokenBasedRememberMeServicesTests.class);
}
public void testAutoLoginIfDoesNotPresentAnyCookies()
throws Exception {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(null, true));
services.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest("dc");
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNull(returnedCookie); // shouldn't try to invalidate our cookie
}
public void testAutoLoginIfDoesNotPresentRequiredCookie()
throws Exception {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(null, true));
services.afterPropertiesSet();
Cookie cookie = new Cookie("unrelated_cookie", "foobar");
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNull(returnedCookie); // shouldn't try to invalidate our cookie
}
public void testAutoLoginIfExpired() throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis()
- 1000000, "someone", "password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginIfMissingThreeTokensInCookieValue()
throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
new String(Base64.encodeBase64("x".getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginIfNotBase64Encoded() throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
"NOT_BASE_64_ENCODED");
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginIfSignatureBlocksDoesNotMatchExpectedValue()
throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis()
+ 1000000, "someone", "password", "WRONG_KEY"));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginIfTokenDoesNotContainANumberInCookieValue()
throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
new String(Base64.encodeBase64(
"username:NOT_A_NUMBER:signature".getBytes())));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginIfUserNotFound() throws Exception {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(null, true));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis()
+ 1000000, "someone", "password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNull(result);
Cookie returnedCookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(returnedCookie);
assertEquals(0, returnedCookie.getMaxAge());
}
public void testAutoLoginWithValidToken() throws Exception {
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setKey("key");
services.setAuthenticationDao(new MockAuthenticationDao(user, false));
services.afterPropertiesSet();
Cookie cookie = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY,
generateCorrectCookieContentForToken(System.currentTimeMillis()
+ 1000000, "someone", "password", "key"));
MockHttpServletRequest request = new MockHttpServletRequest(null, null,
"null", new Cookie[] {cookie});
MockHttpServletResponse response = new MockHttpServletResponse();
Authentication result = services.autoLogin(request, response);
assertNotNull(result);
UserDetails resultingUserDetails = (UserDetails) result.getPrincipal();
assertEquals(user, resultingUserDetails);
}
public void testGettersSetters() {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
services.setAuthenticationDao(new MockAuthenticationDao(null, false));
assertTrue(services.getAuthenticationDao() != null);
services.setKey("d");
assertEquals("d", services.getKey());
assertEquals(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
services.getParameter());
services.setParameter("some_param");
assertEquals("some_param", services.getParameter());
services.setTokenValiditySeconds(12);
assertEquals(12, services.getTokenValiditySeconds());
}
public void testLoginFail() {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest("fv");
MockHttpServletResponse response = new MockHttpServletResponse();
services.loginFail(request, response);
Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(cookie);
assertEquals(0, cookie.getMaxAge());
}
public void testLoginSuccessIgnoredIfParameterNotSetOrFalse() {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest("d");
request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
"false");
MockHttpServletResponse response = new MockHttpServletResponse();
services.loginSuccess(request, response,
new TestingAuthenticationToken("someone", "password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNull(cookie);
}
public void testLoginSuccessNormalWithNonUserDetailsBasedPrincipal() {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest("d");
request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
"true");
MockHttpServletResponse response = new MockHttpServletResponse();
services.loginSuccess(request, response,
new TestingAuthenticationToken("someone", "password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(cookie);
assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years
assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes()));
assertTrue(new Date().before(
new Date(determineExpiryTimeFromBased64EncodedToken(
cookie.getValue()))));
}
public void testLoginSuccessNormalWithUserDetailsBasedPrincipal() {
TokenBasedRememberMeServices services = new TokenBasedRememberMeServices();
MockHttpServletRequest request = new MockHttpServletRequest("d");
request.setParameter(TokenBasedRememberMeServices.DEFAULT_PARAMETER,
"true");
MockHttpServletResponse response = new MockHttpServletResponse();
UserDetails user = new User("someone", "password", true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")});
services.loginSuccess(request, response,
new TestingAuthenticationToken(user, "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ABC")}));
Cookie cookie = response.getCookieByName(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY);
assertNotNull(cookie);
assertEquals(60 * 60 * 24 * 365 * 5, cookie.getMaxAge()); // 5 years
assertTrue(Base64.isArrayByteBase64(cookie.getValue().getBytes()));
assertTrue(new Date().before(
new Date(determineExpiryTimeFromBased64EncodedToken(
cookie.getValue()))));
}
private long determineExpiryTimeFromBased64EncodedToken(String validToken) {
String cookieAsPlainText = new String(Base64.decodeBase64(
validToken.getBytes()));
String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieAsPlainText,
":");
if (cookieTokens.length == 3) {
try {
return new Long(cookieTokens[1]).longValue();
} catch (NumberFormatException nfe) {}
}
return -1;
}
private String generateCorrectCookieContentForToken(long expiryTime,
String username, String password, String key) {
// format is:
// username + ":" + expiryTime + ":" + Md5Hex(username + ":" + expiryTime + ":" + password + ":" + key)
String signatureValue = new String(DigestUtils.md5Hex(username + ":"
+ expiryTime + ":" + password + ":" + key));
String tokenValue = username + ":" + expiryTime + ":" + signatureValue;
String tokenValueBase64 = new String(Base64.encodeBase64(
tokenValue.getBytes()));
return tokenValueBase64;
}
//~ Inner Classes ==========================================================
private class MockAuthenticationDao implements AuthenticationDao {
private UserDetails toReturn;
private boolean throwException;
public MockAuthenticationDao(UserDetails toReturn,
boolean throwException) {
this.toReturn = toReturn;
this.throwException = throwException;
}
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
if (throwException) {
throw new UsernameNotFoundException("as requested by mock");
}
return toReturn;
}
}
}