Added remember-me services.
This commit is contained in:
parent
0d33b06990
commit
f1e071b0f1
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
</value>
|
||||
</property>
|
||||
|
@ -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><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></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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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");
|
||||
%>
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
-->
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue