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

View File

@ -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 ================================================================

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Authentication provider that processes <code>RememberMeAuthenticationToken</code>s.
</body>
</html>

View File

@ -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));
}

View File

@ -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) {}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -0,0 +1,5 @@
<html>
<body>
Support for remembering a user between different web sessions.
</body>
</html>

View File

@ -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());
}
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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));
}
}

View File

@ -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());
}
}

View File

@ -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());

View File

@ -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);
}
}

View File

@ -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) {
}
}
}

View File

@ -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;
}
}
}

View File

@ -2645,7 +2645,7 @@ key: A private key to prevent modification of the nonce token
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
/**=ROLE_USER
&lt;/value&gt;
&lt;/property&gt;
@ -2669,6 +2669,110 @@ key: A private key to prevent modification of the nonce token
authentication mechanism.</para>
</sect2>
<sect2 id="security-ui-remember-me">
<title>Remember-Me Authentication</title>
<para>Remember-me authentication refers to web sites being able to
remember the identity of a principal between sessions. This is
typically accomplished by sending a cookie to the browser, with the
cookie being detected during future sessions and causing automated
login to take place. Acegi Security provides the necessary hooks so
that such operations can take place, along with providing a concrete
implementation that uses hashing to preserve the security of
cookie-based tokens. </para>
<para>Remember-me authentication is not used with digest or basic
authentication, given they are often not used with
<literal>HttpSession</literal>s. Remember-me is used with
<literal>AuthenticationProcessingFilter</literal>, and is implemented
via hooks in the <literal>AbstractProcessingFilter</literal>
superclass. The hooks will invoke a concrete
<literal>RememberMeServices</literal> at the appropriate times. The
interface looks like this:</para>
<para><programlisting>public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response);
public void loginFail(HttpServletRequest request, HttpServletResponse response);
public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication);</programlisting></para>
<para>Please refer to JavaDocs for a fuller discussion on what the
methods do, although note at this stage
<literal>AbstractProcessingFilter</literal> only calls the
<literal>loginFail()</literal> and <literal>loginSuccess()</literal>
methods. The <literal>autoLogin()</literal> method is called by
<literal>RememberMeProcessingFilter</literal> whenever the
<literal>ContextHolder</literal> does not contain an
<literal>Authentication</literal>. This interface therefore provides
the underlaying remember-me implementation with sufficient
notification of authentication-related events, and delegates to the
implementation whenever a candidate web request might contain a cookie
and wish to be remembered.</para>
<para>This design allows any number of remember-me implementation
strategies. In the interests of simplicity and avoiding the need for
DAO implementations that specify write and create methods, Acegi
Security's only concrete implementation,
<literal>TokenBasedRememberMeServices</literal>, uses hashing to
achieve a useful remember-me strategy. In essence a cookie is sent to
the browser upon successful interactive authentication, with that
cookie being composed as follows:</para>
<para><programlisting>base64(username + ":" + expirationTime + ":" + md5Hex(username + ":" + expirationTime + ":" password + ":" + key))
username: As identifiable to TokenBasedRememberMeServices.getAuthenticationDao()
password: That matches the relevant UserDetails retrieved from TokenBasedRememberMeServices.getAuthenticationDao()
expirationTime: The date and time when the remember-me token expires, expressed in milliseconds
key: A private key to prevent modification of the remember-me token
</programlisting></para>
<para>As such the remember-me token is valid only for the period
specified, and provided that the username, password and key does not
change. Notably, this has a potential security issue issue in that a
captured remember-me token will be usable from any user agent until
such time as the token expires. This is the same issue as with digest
authentication. If a principal is aware a token has been captured,
they can easily change their password and immediately invalidate all
remember-me tokens on issue. However, if more significant security is
needed a rolling token approach should be used (this would require a
database) or remember-me services should simply not be used.</para>
<para><literal>TokenBasedRememberMeServices</literal> generates a
<literal>RememberMeAuthenticationToken</literal>, which is processed
by <literal>RememberMeAuthenticationProvider</literal>. A
<literal>key</literal> is shared between this authentication provider
and the <literal>TokenBasedRememberMeServices</literal>. In addition,
<literal>TokenBasedRememberMeServices</literal> requires an
<literal>AuthenticationDao</literal> from which it can retrieve the
username and password for signature comparison purposes, and generate
the <literal>RememberMeAuthenticationToken</literal> to contain the
correct <literal>GrantedAuthority</literal>[]s. Some sort of logout
command should be provided by the application (typically via a JSP)
that invalidates the cookie upon user request. See the Contacts Sample
application's <literal>logout.jsp</literal> for an example.</para>
<para>The beans required in an application context to enable
remember-me services are as follows:</para>
<para><programlisting>&lt;bean id="rememberMeProcessingFilter" class="net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter"&gt;
&lt;property name="rememberMeServices"&gt;&lt;ref local="rememberMeServices"/&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="rememberMeServices" class="net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices"&gt;
&lt;property name="authenticationDao"&gt;&lt;ref local="jdbcDaoImpl"/&gt;&lt;/property&gt;
&lt;property name="key"&gt;&lt;value&gt;springRocks&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="rememberMeAuthenticationProvider" class="net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider"&gt;
&lt;property name="key"&gt;&lt;value&gt;springRocks&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;</programlisting>Don't forget to add your
<literal>RememberMeServices</literal> implementation to your
<literal>AuthenticationProcessingFilter.setRememberMeServices()</literal>
property, include the <literal>RememberMeProcessingFilter</literal> in
your <literal>AuthenticationManager.setProviders()</literal> list, and
add a call to <literal>RememberMeProcessingFilter</literal> into your
<literal>FilterChainProxy</literal> (typically immediately after your
<literal>AuthenticationProcessingFilter</literal>).</para>
</sect2>
<sect2 id="security-ui-well-known">
<title>Well-Known Locations</title>
@ -4482,6 +4586,15 @@ INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
container</para>
</listitem>
<listitem>
<para><literal>RememberMeProcessingFilter</literal>, so that if no
earlier authentication processing mechanism updated the
<literal>ContextHolder</literal>, and the request presents a
cookie that enables remember-me services to take place, a suitable
remembered <literal><literal>Authentication</literal></literal>
object will be put there</para>
</listitem>
<listitem>
<para><literal>AnonymousProcessingFilter</literal>, so that if no
earlier authentication processing mechanism updated the

View File

@ -27,27 +27,29 @@
<body>
<release version="0.8.0" date="CVS">
<action dev="benalex" type="add">Added Digest Authentication support (RFC 2617 and RFC 2069)</action>
<action dev="benalex" type="add">Added pluggable remember-me services</action>
<action dev="benalex" type="add">Added pluggable mechnism to prevent concurrent login sessions</action>
<action dev="benalex" type="add">FilterChainProxy added to significantly simplify web.xml configuration of Acegi Security</action>
<action dev="benalex" type="add">AuthenticationProcessingFilter now provides hook for extra credentials (eg postcodes)</action>
<action dev="benalex" type="add">New WebAuthenticationDetails class now used by processing filters for Authentication.setDetails()</action>
<action dev="benalex" type="add">Additional debug-level logging</action>
<action dev="benalex" type="add">Improved Tapestry support in AbstractProcessingFilter</action>
<action dev="benalex" type="update">Made ConfigAttributeDefinition and ConfigAttribute Serializable</action>
<action dev="benalex" type="update">User now accepts blank passwords (null passwords still rejected)</action>
<action dev="benalex" type="update">FilterToBeanProxy now searches hierarchical bean factories</action>
<action dev="benalex" type="add">Improved Tapestry support in AbstractProcessingFilter</action>
<action dev="benalex" type="update">User now accepted blank passwords (null passwords still rejected)</action>
<action dev="benalex" type="update">ContextHolderAwareRequestWrapper now provides a getUserPrincipal() method</action>
<action dev="benalex" type="update">HttpSessionIntegrationFilter no longer creates a HttpSession unnecessarily</action>
<action dev="benalex" type="update">FilterSecurityInterceptor now only executes once per request (improves performance with SiteMesh)</action>
<action dev="benalex" type="fix">Log4j now included in generated WAR artifacts (fixes issue with Log4j listener)</action>
<action dev="raykrueger" type="update">JaasAuthenticatinProvider now uses System.property "java.security.auth.login.config"</action>
<action dev="raykrueger" type="update">JaasAuthenticationCallbackHandler Authentication is passed to handle method setAuthentication removed</action>
<action dev="raykrueger" type="update">Added AuthenticationException to the AutenticationEntryPoint.commence method signature</action>
<action dev="raykrueger" type="update">Added AccessDeniedException to the SecurityEncorcementFilter.sendAccessDeniedError method signature</action>
<action dev="benalex" type="fix">Correct issue with JdbcDaoImpl default SQL query not using consistent case sensitivity</action>
<action dev="benalex" type="add">FilterChainProxy added to significantly simplify web.xml configuration of Acegi Security</action>
<action dev="benalex" type="update">FilterToBeanProxy now addresses lifecycle mismatch (IoC container vs servlet container) issue</action>
<action dev="benalex" type="add">Additional debug-level logging</action>
<action dev="benalex" type="add">AuthenticationProcessingFilter now provides hook for extra credentials (eg postcodes)</action>
<action dev="benalex" type="add">New WebAuthenticationDetails class now used by processing filters for Authentication.setDetails()</action>
<action dev="benalex" type="update">Significantly refactor "well-known location model" to authentication processing mechanism and HttpSessionContextIntegrationFilter model</action>
<action dev="benalex" type="fix">Correct issue with JdbcDaoImpl default SQL query not using consistent case sensitivity</action>
<action dev="benalex" type="fix">Improve Linux and non-Sun JDK (specifically IBM JDK) compatibility</action>
<action dev="benalex" type="fix">Log4j now included in generated WAR artifacts (fixes issue with Log4j listener)</action>
<action dev="benalex" type="fix">Correct NullPointerException in FilterInvocationDefinitionSource implementations</action>
</release>
<release version="0.7.0" date="2005-01-16">

View File

@ -13,6 +13,7 @@ log4j.rootLogger=WARN, stdout, fileout
#log4j.logger.net.sf.acegisecurity.acl.basic=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.taglibs.authz=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.ui.basicauth=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.ui.rememberme=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.ui=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.afterinvocation=DEBUG, stdout, fileout
#log4j.logger.net.sf.acegisecurity.ui.rmi=DEBUG, stdout, fileout

View File

@ -25,6 +25,11 @@
the application context using standard Acegi Security classes. *</li>
<li><b>Database-sourced security data</b>. All of the user, role and ACL
information is obtained from an in-memory JDBC-compliant database.</li>
<li><b>Integrated form-based and BASIC authentication</b>. Any BASIC
authentication header is detected and used for authentication. Normal
interactive form-based authentication is used by default.</li>
<li><b>Remember-me services</b>. Acegi Security's pluggable remember-me
strategy is demonstrated, with a corresponding checkbox on the login form.</li>
</ul>
* As the application provides an "ACL Administration" use case, those

View File

@ -27,6 +27,6 @@
</tr>
</c:forEach>
</table>
<p><a href="<c:url value="add.htm"/>">Add</a> <p><a href="<c:url value="../logoff.jsp"/>">Logoff</a>
<p><a href="<c:url value="add.htm"/>">Add</a> <p><a href="<c:url value="../logoff.jsp"/>">Logoff</a> (also clears any remember-me cookie)
</body>
</html>

View File

@ -1,3 +1,9 @@
<%session.invalidate();
<%@ page import="javax.servlet.http.Cookie" %>
<%@ page import="net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices" %>
<%
session.invalidate();
Cookie terminate = new Cookie(TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, null);
terminate.setMaxAge(0);
response.addCookie(terminate);
response.sendRedirect("index.jsp");
%>

View File

@ -21,7 +21,7 @@
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
</value>
</property>
</bean>
@ -33,6 +33,7 @@
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
<ref local="rememberMeAuthenticationProvider"/>
</list>
</property>
</bean>
@ -89,6 +90,19 @@
<property name="context"><value>net.sf.acegisecurity.context.security.SecureContextImpl</value></property>
</bean>
<bean id="rememberMeProcessingFilter" class="net.sf.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property name="rememberMeServices"><ref local="rememberMeServices"/></property>
</bean>
<bean id="rememberMeServices" class="net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property name="authenticationDao"><ref local="jdbcDaoImpl"/></property>
<property name="key"><value>springRocks</value></property>
</bean>
<bean id="rememberMeAuthenticationProvider" class="net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property name="key"><value>springRocks</value></property>
</bean>
<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
<!-- You will need to uncomment the "Acegi Channel Processing Filter"
@ -131,6 +145,7 @@
<property name="authenticationFailureUrl"><value>/acegilogin.jsp?login_error=1</value></property>
<property name="defaultTargetUrl"><value>/</value></property>
<property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
<property name="rememberMeServices"><ref local="rememberMeServices"/></property>
</bean>
<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
@ -160,7 +175,7 @@
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
/**=ROLE_USER
</value>
</property>

View File

@ -62,11 +62,11 @@
The HttpSessionEventPublisher will publish
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
to the WebApplicationContext
-->
<listener>
<listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
-->
<!--
- Provides core MVC application controller. See contacts-servlet.xml.
-->

View File

@ -33,6 +33,7 @@
<table>
<tr><td>User:</td><td><input type='text' name='j_username' <c:if test="${not empty param.login_error}">value='<%= session.getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY) %>'</c:if>></td></tr>
<tr><td>Password:</td><td><input type='password' name='j_password'></td></tr>
<tr><td><input type="checkbox" name="_acegi_security_remember_me"></td><td>Don't ask for my password for two weeks</td></tr>
<tr><td colspan='2'><input name="submit" type="submit"></td></tr>
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>