Added remember-me services.
This commit is contained in:
parent
0d33b06990
commit
f1e071b0f1
|
@ -16,6 +16,7 @@
|
||||||
package net.sf.acegisecurity;
|
package net.sf.acegisecurity;
|
||||||
|
|
||||||
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
|
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
|
||||||
|
import net.sf.acegisecurity.providers.rememberme.RememberMeAuthenticationToken;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,7 +40,7 @@ public class AuthenticationTrustResolverImpl
|
||||||
//~ Instance fields ========================================================
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
private Class anonymousClass = AnonymousAuthenticationToken.class;
|
private Class anonymousClass = AnonymousAuthenticationToken.class;
|
||||||
private Class rememberMeClass;
|
private Class rememberMeClass = RememberMeAuthenticationToken.class;
|
||||||
|
|
||||||
//~ Methods ================================================================
|
//~ 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.SecureContext;
|
||||||
import net.sf.acegisecurity.context.security.SecureContextUtils;
|
import net.sf.acegisecurity.context.security.SecureContextUtils;
|
||||||
import net.sf.acegisecurity.providers.cas.ProxyUntrustedException;
|
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.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
|
@ -106,6 +110,7 @@ public abstract class AbstractProcessingFilter implements Filter,
|
||||||
//~ Instance fields ========================================================
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
private AuthenticationManager authenticationManager;
|
private AuthenticationManager authenticationManager;
|
||||||
|
private RememberMeServices rememberMeServices = new NullRememberMeServices();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Where to redirect the browser if authentication fails due to incorrect
|
* 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 abstract String getDefaultFilterProcessesUrl();
|
||||||
|
|
||||||
|
public void setRememberMeServices(RememberMeServices rememberMeServices) {
|
||||||
|
this.rememberMeServices = rememberMeServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RememberMeServices getRememberMeServices() {
|
||||||
|
return rememberMeServices;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs actual authentication.
|
* Performs actual authentication.
|
||||||
*
|
*
|
||||||
|
@ -306,6 +319,8 @@ public abstract class AbstractProcessingFilter implements Filter,
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"authenticationManager must be specified");
|
"authenticationManager must be specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert.notNull(this.rememberMeServices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -370,7 +385,8 @@ public abstract class AbstractProcessingFilter implements Filter,
|
||||||
HttpServletResponse response) throws IOException {}
|
HttpServletResponse response) throws IOException {}
|
||||||
|
|
||||||
protected void onSuccessfulAuthentication(HttpServletRequest request,
|
protected void onSuccessfulAuthentication(HttpServletRequest request,
|
||||||
HttpServletResponse response) throws IOException {}
|
HttpServletResponse response, Authentication authResult)
|
||||||
|
throws IOException {}
|
||||||
|
|
||||||
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
|
protected void onUnsuccessfulAuthentication(HttpServletRequest request,
|
||||||
HttpServletResponse response) throws IOException {}
|
HttpServletResponse response) throws IOException {}
|
||||||
|
@ -429,7 +445,9 @@ public abstract class AbstractProcessingFilter implements Filter,
|
||||||
+ targetUrl);
|
+ targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccessfulAuthentication(request, response);
|
onSuccessfulAuthentication(request, response, authResult);
|
||||||
|
|
||||||
|
rememberMeServices.loginSuccess(request, response, authResult);
|
||||||
|
|
||||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||||
}
|
}
|
||||||
|
@ -481,6 +499,8 @@ public abstract class AbstractProcessingFilter implements Filter,
|
||||||
|
|
||||||
onUnsuccessfulAuthentication(request, response);
|
onUnsuccessfulAuthentication(request, response);
|
||||||
|
|
||||||
|
rememberMeServices.loginFail(request, response);
|
||||||
|
|
||||||
response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
|
response.sendRedirect(response.encodeRedirectURL(request.getContextPath()
|
||||||
+ failureUrl));
|
+ 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.TestingAuthenticationToken;
|
||||||
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
|
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")})));
|
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() {
|
public void testGettersSetters() {
|
||||||
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
|
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
|
||||||
|
@ -62,6 +73,9 @@ public class AuthenticationTrustResolverImplTests extends TestCase {
|
||||||
trustResolver.setAnonymousClass(String.class);
|
trustResolver.setAnonymousClass(String.class);
|
||||||
assertEquals(String.class, trustResolver.getAnonymousClass());
|
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 attribMap = new HashMap();
|
||||||
private Map headersMap = new HashMap();
|
private Map headersMap = new HashMap();
|
||||||
private Map paramMap = new HashMap();
|
private Map paramMap = new HashMap();
|
||||||
|
private Map cookiesMap = new HashMap();
|
||||||
private Principal principal;
|
private Principal principal;
|
||||||
private String contextPath = "";
|
private String contextPath = "";
|
||||||
private String pathInfo; // null for no extra path
|
private String pathInfo; // null for no extra path
|
||||||
|
@ -75,6 +76,15 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||||
this.queryString = queryString;
|
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,
|
public MockHttpServletRequest(Map headers, Principal principal,
|
||||||
HttpSession session) {
|
HttpSession session) {
|
||||||
this.headersMap = headers;
|
this.headersMap = headers;
|
||||||
|
@ -129,7 +139,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cookie[] getCookies() {
|
public Cookie[] getCookies() {
|
||||||
throw new UnsupportedOperationException("mock method not implemented");
|
return (Cookie[]) cookiesMap.values().toArray(new Cookie[] {});
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDateHeader(String arg0) {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 {
|
public class MockHttpServletResponse implements HttpServletResponse {
|
||||||
//~ Instance fields ========================================================
|
//~ Instance fields ========================================================
|
||||||
|
|
||||||
|
private Map cookiesMap = new HashMap();
|
||||||
private Map headersMap = new HashMap();
|
private Map headersMap = new HashMap();
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
private String redirect;
|
private String redirect;
|
||||||
|
@ -72,6 +73,10 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||||
throw new UnsupportedOperationException("mock method not implemented");
|
throw new UnsupportedOperationException("mock method not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cookie getCookieByName(String name) {
|
||||||
|
return (Cookie) cookiesMap.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
public void setDateHeader(String arg0, long arg1) {
|
public void setDateHeader(String arg0, long arg1) {
|
||||||
throw new UnsupportedOperationException("mock method not implemented");
|
throw new UnsupportedOperationException("mock method not implemented");
|
||||||
}
|
}
|
||||||
|
@ -131,7 +136,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCookie(Cookie arg0) {
|
public void addCookie(Cookie arg0) {
|
||||||
throw new UnsupportedOperationException("mock method not implemented");
|
cookiesMap.put(arg0.getName(), arg0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDateHeader(String arg0, long arg1) {
|
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.SecureContextImpl;
|
||||||
import net.sf.acegisecurity.context.security.SecureContextUtils;
|
import net.sf.acegisecurity.context.security.SecureContextUtils;
|
||||||
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
|
||||||
|
import net.sf.acegisecurity.ui.rememberme.TokenBasedRememberMeServices;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -150,6 +151,11 @@ public class AbstractProcessingFilterTests extends TestCase {
|
||||||
|
|
||||||
public void testGettersSetters() {
|
public void testGettersSetters() {
|
||||||
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
|
AbstractProcessingFilter filter = new MockAbstractProcessingFilter();
|
||||||
|
assertNotNull(filter.getRememberMeServices());
|
||||||
|
filter.setRememberMeServices(new TokenBasedRememberMeServices());
|
||||||
|
assertEquals(TokenBasedRememberMeServices.class,
|
||||||
|
filter.getRememberMeServices().getClass());
|
||||||
|
|
||||||
filter.setAuthenticationFailureUrl("/x");
|
filter.setAuthenticationFailureUrl("/x");
|
||||||
assertEquals("/x", filter.getAuthenticationFailureUrl());
|
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
|
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
|
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
|
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
|
/acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/**=ROLE_USER
|
/**=ROLE_USER
|
||||||
</value>
|
</value>
|
||||||
</property>
|
</property>
|
||||||
|
@ -2669,6 +2669,110 @@ key: A private key to prevent modification of the nonce token
|
||||||
authentication mechanism.</para>
|
authentication mechanism.</para>
|
||||||
</sect2>
|
</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">
|
<sect2 id="security-ui-well-known">
|
||||||
<title>Well-Known Locations</title>
|
<title>Well-Known Locations</title>
|
||||||
|
|
||||||
|
@ -4482,6 +4586,15 @@ INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
|
||||||
container</para>
|
container</para>
|
||||||
</listitem>
|
</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>
|
<listitem>
|
||||||
<para><literal>AnonymousProcessingFilter</literal>, so that if no
|
<para><literal>AnonymousProcessingFilter</literal>, so that if no
|
||||||
earlier authentication processing mechanism updated the
|
earlier authentication processing mechanism updated the
|
||||||
|
|
|
@ -27,27 +27,29 @@
|
||||||
<body>
|
<body>
|
||||||
<release version="0.8.0" date="CVS">
|
<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 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">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">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="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">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">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">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="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">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">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 AuthenticationException to the AutenticationEntryPoint.commence method signature</action>
|
||||||
<action dev="raykrueger" type="update">Added AccessDeniedException to the SecurityEncorcementFilter.sendAccessDeniedError 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="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="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">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>
|
<action dev="benalex" type="fix">Correct NullPointerException in FilterInvocationDefinitionSource implementations</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="0.7.0" date="2005-01-16">
|
<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.acl.basic=DEBUG, stdout, fileout
|
||||||
#log4j.logger.net.sf.acegisecurity.taglibs.authz=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.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.ui=DEBUG, stdout, fileout
|
||||||
#log4j.logger.net.sf.acegisecurity.afterinvocation=DEBUG, stdout, fileout
|
#log4j.logger.net.sf.acegisecurity.afterinvocation=DEBUG, stdout, fileout
|
||||||
#log4j.logger.net.sf.acegisecurity.ui.rmi=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>
|
the application context using standard Acegi Security classes. *</li>
|
||||||
<li><b>Database-sourced security data</b>. All of the user, role and ACL
|
<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>
|
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>
|
</ul>
|
||||||
|
|
||||||
* As the application provides an "ACL Administration" use case, those
|
* As the application provides an "ACL Administration" use case, those
|
||||||
|
|
|
@ -27,6 +27,6 @@
|
||||||
</tr>
|
</tr>
|
||||||
</c:forEach>
|
</c:forEach>
|
||||||
</table>
|
</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>
|
</body>
|
||||||
</html>
|
</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");
|
response.sendRedirect("index.jsp");
|
||||||
%>
|
%>
|
|
@ -21,7 +21,7 @@
|
||||||
<value>
|
<value>
|
||||||
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
|
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
|
||||||
PATTERN_TYPE_APACHE_ANT
|
PATTERN_TYPE_APACHE_ANT
|
||||||
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
|
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
|
||||||
</value>
|
</value>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
@ -33,6 +33,7 @@
|
||||||
<list>
|
<list>
|
||||||
<ref local="daoAuthenticationProvider"/>
|
<ref local="daoAuthenticationProvider"/>
|
||||||
<ref local="anonymousAuthenticationProvider"/>
|
<ref local="anonymousAuthenticationProvider"/>
|
||||||
|
<ref local="rememberMeAuthenticationProvider"/>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
@ -89,6 +90,19 @@
|
||||||
<property name="context"><value>net.sf.acegisecurity.context.security.SecureContextImpl</value></property>
|
<property name="context"><value>net.sf.acegisecurity.context.security.SecureContextImpl</value></property>
|
||||||
</bean>
|
</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 ==================== -->
|
<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
|
||||||
|
|
||||||
<!-- You will need to uncomment the "Acegi Channel Processing Filter"
|
<!-- 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="authenticationFailureUrl"><value>/acegilogin.jsp?login_error=1</value></property>
|
||||||
<property name="defaultTargetUrl"><value>/</value></property>
|
<property name="defaultTargetUrl"><value>/</value></property>
|
||||||
<property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
|
<property name="filterProcessesUrl"><value>/j_acegi_security_check</value></property>
|
||||||
|
<property name="rememberMeServices"><ref local="rememberMeServices"/></property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
|
<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
|
||||||
|
@ -160,7 +175,7 @@
|
||||||
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
|
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
|
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
|
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
|
/acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
|
||||||
/**=ROLE_USER
|
/**=ROLE_USER
|
||||||
</value>
|
</value>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -62,11 +62,11 @@
|
||||||
The HttpSessionEventPublisher will publish
|
The HttpSessionEventPublisher will publish
|
||||||
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
|
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
|
||||||
to the WebApplicationContext
|
to the WebApplicationContext
|
||||||
-->
|
|
||||||
<listener>
|
<listener>
|
||||||
<listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
|
<listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
-->
|
||||||
<!--
|
<!--
|
||||||
- Provides core MVC application controller. See contacts-servlet.xml.
|
- Provides core MVC application controller. See contacts-servlet.xml.
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<table>
|
<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>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>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="submit" type="submit"></td></tr>
|
||||||
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
<tr><td colspan='2'><input name="reset" type="reset"></td></tr>
|
||||||
|
|
Loading…
Reference in New Issue