Anonymous principal support. As requested by the community at various times, including in http://forum.springframework.org/viewtopic.php?t=1925.

This commit is contained in:
Ben Alex 2005-02-23 06:09:56 +00:00
parent 3c4faf58c7
commit 693ac5a24a
21 changed files with 1467 additions and 135 deletions

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -32,10 +32,14 @@ public interface AccessDecisionManager {
* @param config the configuration attributes associated with the secured
* object being invoked
*
* @throws AccessDeniedException if access is denied
* @throws AccessDeniedException if access is denied as the authentication
* does not hold a required authority or ACL privilege
* @throws InsufficientAuthenticationException if access is denied as the
* authentication does not provide a sufficient level of trust
*/
public void decide(Authentication authentication, Object object,
ConfigAttributeDefinition config) throws AccessDeniedException;
ConfigAttributeDefinition config)
throws AccessDeniedException, InsufficientAuthenticationException;
/**
* Indicates whether this <code>AccessDecisionManager</code> is able to

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,9 +16,8 @@
package net.sf.acegisecurity;
/**
* Abstract superclass for all exceptions related to the {@link
* AuthenticationManager} being unable to authenticate an {@link
* Authentication} object.
* Abstract superclass for all exceptions related an {@link Authentication}
* object being invalid for whatever reason.
*
* @author Ben Alex
* @version $Id$
@ -57,11 +56,11 @@ public abstract class AuthenticationException extends AcegiSecurityException {
//~ Methods ================================================================
void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
public Authentication getAuthentication() {
return authentication;
}
void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}

View File

@ -0,0 +1,66 @@
/* 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;
/**
* Evaluates <code>Authentication</code> tokens
*
* @author Ben Alex
* @version $Id$
*/
public interface AuthenticationTrustResolver {
//~ Methods ================================================================
/**
* Indicates whether the passed <code>Authentication</code> token
* represents an anonymous user. Typically the framework will call this
* method if it is trying to decide whether an
* <code>AccessDeniedException</code> should result in a final rejection
* (ie as would be the case if the principal was non-anonymous/fully
* authenticated) or direct the principal to attempt actual authentication
* (ie as would be the case if the <code>Authentication</code> was merely
* anonymous).
*
* @param authentication to test (may be <code>null</code> in which case
* the method will always return <code>false</code>)
*
* @return <code>true</code> the passed authentication token represented an
* anonymous principal, <code>false</code> otherwise
*/
public boolean isAnonymous(Authentication authentication);
/**
* Indicates whether the passed <code>Authentication</code> token
* represents user that has been remembered (ie not a user that has been
* fully authenticated).
*
* <p>
* <b>No part of the framework uses this method</b>, as it is a weak
* definition of trust levels. The method is provided simply to assist
* with custom <code>AccessDecisionVoter</code>s and the like that you
* might develop. Of course, you don't need to use this method either and
* can develop your own "trust level" hierarchy instead.
* </p>
*
* @param authentication to test (may be <code>null</code> in which case
* the method will always return <code>false</code>)
*
* @return <code>true</code> the passed authentication token represented a
* principal authenticated using a remember-me token,
* <code>false</code> otherwise
*/
public boolean isRememberMe(Authentication authentication);
}

View File

@ -0,0 +1,77 @@
/* 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;
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
/**
* Basic implementation of {@link AuthenticationTrustResolverImpl}.
*
* <P>
* Makes trust decisions based on whether the passed
* <code>Authentication</code> is an instance of a defined class.
* </p>
*
* <p>
* If {@link #anonymousClass} or {@link #rememberMeClass} is <code>null</code>,
* the corresponding method will always return <code>false</code>.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationTrustResolverImpl
implements AuthenticationTrustResolver {
//~ Instance fields ========================================================
private Class anonymousClass = AnonymousAuthenticationToken.class;
private Class rememberMeClass;
//~ Methods ================================================================
public boolean isAnonymous(Authentication authentication) {
if ((anonymousClass == null) || (authentication == null)) {
return false;
}
return anonymousClass.isAssignableFrom(authentication.getClass());
}
public void setAnonymousClass(Class anonymousClass) {
this.anonymousClass = anonymousClass;
}
public Class getAnonymousClass() {
return anonymousClass;
}
public boolean isRememberMe(Authentication authentication) {
if ((rememberMeClass == null) || (authentication == null)) {
return false;
}
return rememberMeClass.isAssignableFrom(authentication.getClass());
}
public void setRememberMeClass(Class rememberMeClass) {
this.rememberMeClass = rememberMeClass;
}
public Class getRememberMeClass() {
return rememberMeClass;
}
}

View File

@ -0,0 +1,58 @@
/* 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;
/**
* Thrown if an authentication request is rejected because the credentials are
* not sufficiently trusted.
*
* <p>
* {{@link net.sf.acegisecurity.vote.AccessDecisionVoter}s will typically throw
* this exception if they are dissatisfied with the level of the
* authentication, such as if performed using a remember-me mechnanism or
* anonymously. The commonly used {@link
* net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter} will thus
* cause the <code>AuthenticationEntryPoint</code> to be called, allowing the
* principal to authenticate with a stronger level of authentication. }
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class InsufficientAuthenticationException extends AuthenticationException {
//~ Constructors ===========================================================
/**
* Constructs an <code>InsufficientAuthenticationException</code> with the
* specified message.
*
* @param msg the detail message
*/
public InsufficientAuthenticationException(String msg) {
super(msg);
}
/**
* Constructs an <code>InsufficientAuthenticationException</code> with the
* specified message and root cause.
*
* @param msg the detail message
* @param t root cause
*/
public InsufficientAuthenticationException(String msg, Throwable t) {
super(msg, t);
}
}

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -17,6 +17,9 @@ package net.sf.acegisecurity.intercept.web;
import net.sf.acegisecurity.AccessDeniedException;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationTrustResolver;
import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.ui.AbstractProcessingFilter;
import net.sf.acegisecurity.util.PortResolver;
import net.sf.acegisecurity.util.PortResolverImpl;
@ -26,6 +29,8 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import javax.servlet.Filter;
@ -54,10 +59,13 @@ import javax.servlet.http.HttpServletResponse;
* </p>
*
* <p>
* If an {@link AccessDeniedException} is detected, the filter will respond
* with a <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error). In
* addition, the <code>AccessDeniedException</code> itself will be placed in
* the <code>HttpSession</code> attribute keyed against {@link
* If an {@link AccessDeniedException} is detected, the filter will determine
* whether or not the user is an anonymous user. If they are an anonymous
* user, the <code>authenticationEntryPoint</code> will be launched. If they
* are not an anonymous user, the filter will respond with a
* <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error). In addition,
* the <code>AccessDeniedException</code> itself will be placed in the
* <code>HttpSession</code> attribute keyed against {@link
* #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY} (to allow access to the stack
* trace etc). Again, this allows common access denied handling irrespective
* of the originating security interceptor.
@ -104,6 +112,7 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
//~ Instance fields ========================================================
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
private FilterSecurityInterceptor filterSecurityInterceptor;
private PortResolver portResolver = new PortResolverImpl();
@ -118,6 +127,15 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
return authenticationEntryPoint;
}
public void setAuthenticationTrustResolver(
AuthenticationTrustResolver authenticationTrustResolver) {
this.authenticationTrustResolver = authenticationTrustResolver;
}
public AuthenticationTrustResolver getAuthenticationTrustResolver() {
return authenticationTrustResolver;
}
public void setFilterSecurityInterceptor(
FilterSecurityInterceptor filterSecurityInterceptor) {
this.filterSecurityInterceptor = filterSecurityInterceptor;
@ -136,19 +154,13 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
}
public void afterPropertiesSet() throws Exception {
if (authenticationEntryPoint == null) {
throw new IllegalArgumentException(
"authenticationEntryPoint must be specified");
}
if (filterSecurityInterceptor == null) {
throw new IllegalArgumentException(
"filterSecurityInterceptor must be specified");
}
if (portResolver == null) {
throw new IllegalArgumentException("portResolver must be specified");
}
Assert.notNull(authenticationEntryPoint,
"authenticationEntryPoint must be specified");
Assert.notNull(filterSecurityInterceptor,
"filterSecurityInterceptor must be specified");
Assert.notNull(portResolver, "portResolver must be specified");
Assert.notNull(authenticationTrustResolver,
"authenticationTrustResolver must be specified");
}
public void destroy() {}
@ -172,43 +184,29 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
logger.debug("Chain processed normally");
}
} catch (AuthenticationException authentication) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
int port = portResolver.getServerPort(request);
boolean includePort = true;
if ("http".equals(request.getScheme().toLowerCase())
&& (port == 80)) {
includePort = false;
}
if ("https".equals(request.getScheme().toLowerCase())
&& (port == 443)) {
includePort = false;
}
String targetUrl = request.getScheme() + "://"
+ request.getServerName() + ((includePort) ? (":" + port) : "")
+ httpRequest.getContextPath() + fi.getRequestUrl();
if (logger.isDebugEnabled()) {
logger.debug(
"Authentication failed - adding target URL to Session: "
+ targetUrl, authentication);
logger.debug("Authentication exception occurred; redirecting to authentication entry point",
authentication);
}
((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
targetUrl);
authenticationEntryPoint.commence(request, response, authentication);
sendStartAuthentication(fi, authentication);
} catch (AccessDeniedException accessDenied) {
if (logger.isDebugEnabled()) {
logger.debug(
"Access is denied - sending back forbidden response");
}
if (authenticationTrustResolver.isAnonymous(
SecureContextUtils.getSecureContext().getAuthentication())) {
if (logger.isDebugEnabled()) {
logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
accessDenied);
}
((HttpServletRequest) request).getSession().setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
accessDenied);
sendAccessDeniedError(request, response, accessDenied);
sendStartAuthentication(fi, null);
} else {
if (logger.isDebugEnabled()) {
logger.debug("Access is denied (user is not anonymous); sending back forbidden response",
accessDenied);
}
sendAccessDeniedError(fi, accessDenied);
}
} catch (Throwable otherException) {
throw new ServletException(otherException);
}
@ -216,19 +214,43 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
public void init(FilterConfig filterConfig) throws ServletException {}
/**
* Allows subclasses to override if required
*
* @param request
* @param response
* @param accessDenied
*
* @throws IOException
*/
protected void sendAccessDeniedError(ServletRequest request,
ServletResponse response, AccessDeniedException accessDenied)
throws IOException {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN,
protected void sendAccessDeniedError(FilterInvocation fi,
AccessDeniedException accessDenied)
throws ServletException, IOException {
((HttpServletRequest) fi.getRequest()).getSession().setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
accessDenied);
((HttpServletResponse) fi.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
accessDenied.getMessage()); // 403
}
protected void sendStartAuthentication(FilterInvocation fi,
AuthenticationException reason) throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) fi.getRequest();
int port = portResolver.getServerPort(request);
boolean includePort = true;
if ("http".equals(request.getScheme().toLowerCase()) && (port == 80)) {
includePort = false;
}
if ("https".equals(request.getScheme().toLowerCase()) && (port == 443)) {
includePort = false;
}
String targetUrl = request.getScheme() + "://"
+ request.getServerName() + ((includePort) ? (":" + port) : "")
+ request.getContextPath() + fi.getRequestUrl();
if (logger.isDebugEnabled()) {
logger.debug(
"Authentication entry point being called; target URL added to Session: "
+ targetUrl);
}
((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
targetUrl);
authenticationEntryPoint.commence(request,
(HttpServletResponse) fi.getResponse(), reason);
}
}

View File

@ -0,0 +1,86 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.anonymous;
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.anonymous.AnonymousAuthenticationToken}s.
*
* <p>
* To be successfully validated, the {@link
* net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken#getKeyHash()}
* must match this class' {@link #getKey()}.
* </p>
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousAuthenticationProvider implements AuthenticationProvider,
InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(AnonymousAuthenticationProvider.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() != ((AnonymousAuthenticationToken) authentication)
.getKeyHash()) {
throw new BadCredentialsException(
"The presented AnonymousAuthenticationToken does not contain the expected key");
}
return authentication;
}
public boolean supports(Class authentication) {
return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
}
}

View File

@ -0,0 +1,133 @@
/* 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.anonymous;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.providers.AbstractAuthenticationToken;
import java.io.Serializable;
/**
* Represents an anonymous <code>Authentication</code>.
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousAuthenticationToken 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 AnonymousAuthenticationToken(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 AnonymousAuthenticationToken() {
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 AnonymousAuthenticationToken) {
AnonymousAuthenticationToken test = (AnonymousAuthenticationToken) obj;
if (this.getKeyHash() != test.getKeyHash()) {
return false;
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,169 @@
/* 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.anonymous;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextUtils;
import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
import net.sf.acegisecurity.providers.dao.memory.UserAttribute;
import net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
/**
* Detects if there is no <code>Authentication</code> object in the
* <code>ContextHolder</code>, and populates it with one if needed.
*
* <P></p>
*
* <p>
* In summary, this filter is responsible for processing any request that has a
* HTTP request header of <code>Authorization</code> with an authentication
* scheme of <code>Basic</code> and a Base64-encoded
* <code>username:password</code> token. For example, to authenticate user
* "Aladdin" with password "open sesame" the following header would be
* presented:
* </p>
*
* <p>
* <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
* </p>
*
* <p>
* This filter can be used to provide BASIC authentication services to both
* remoting protocol clients (such as Hessian and SOAP) as well as standard
* user agents (such as Internet Explorer and Netscape).
* </p>
*
* <P>
* If authentication is successful, the resulting {@link Authentication} object
* will be placed into the <code>ContextHolder</code>.
* </p>
*
* <p>
* If authentication fails, an {@link AuthenticationEntryPoint} implementation
* is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
* which will prompt the user to authenticate again via BASIC authentication.
* </p>
*
* <P>
* Basic authentication is an attractive protocol because it is simple and
* widely deployed. However, it still transmits a password in clear text and
* as such is undesirable in many situations. Digest authentication is also
* provided by Acegi Security and should be used instead of Basic
* authentication wherever possible. See {@link
* net.sf.acegisecurity.ui.digestauth.DigestProcessingFilter}.
* </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 AnonymousProcessingFilter implements Filter, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(AnonymousProcessingFilter.class);
//~ Instance fields ========================================================
private String key;
private UserAttribute userAttribute;
//~ Methods ================================================================
public void setKey(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public void setUserAttribute(UserAttribute userAttributeDefinition) {
this.userAttribute = userAttributeDefinition;
}
public UserAttribute getUserAttribute() {
return userAttribute;
}
public void afterPropertiesSet() throws Exception {
Assert.notNull(userAttribute);
Assert.hasLength(key);
}
/**
* Does nothing - we reply on IoC lifecycle services instead.
*/
public void destroy() {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
SecureContext sc = SecureContextUtils.getSecureContext();
if (sc.getAuthentication() == null) {
sc.setAuthentication(createAuthentication(request));
if (logger.isDebugEnabled()) {
logger.debug("Replaced ContextHolder with anonymous token: '"
+ sc.getAuthentication() + "'");
}
} else {
if (logger.isDebugEnabled()) {
logger.debug(
"ContextHolder not replaced with anonymous token, as ContextHolder already contained: '"
+ sc.getAuthentication() + "'");
}
}
chain.doFilter(request, response);
}
/**
* Does nothing - we reply on IoC lifecycle services instead.
*
* @param arg0 DOCUMENT ME!
*
* @throws ServletException DOCUMENT ME!
*/
public void init(FilterConfig arg0) throws ServletException {}
protected Authentication createAuthentication(ServletRequest request) {
return new AnonymousAuthenticationToken(key,
userAttribute.getPassword(), userAttribute.getAuthorities());
}
}

View File

@ -0,0 +1,7 @@
<html>
<body>
Allows you to secure every invocation (especially useful for web request
URI security) by always having either an actual principal or an anonymous
principal authenticated.
</body>
</html>

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -29,7 +29,7 @@ import java.util.Vector;
* @author Ben Alex
* @version $Id$
*/
public class UserAttributeDefinition {
public class UserAttribute {
//~ Instance fields ========================================================
private List authorities = new Vector();
@ -38,7 +38,7 @@ public class UserAttributeDefinition {
//~ Constructors ===========================================================
public UserAttributeDefinition() {
public UserAttribute() {
super();
}

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,8 +23,8 @@ import java.beans.PropertyEditorSupport;
/**
* Property editor that creates a {@link UserAttributeDefinition} from a comma
* separated list of values.
* Property editor that creates a {@link UserAttribute} from a comma separated
* list of values.
*
* @author Ben Alex
* @version $Id$
@ -37,7 +37,7 @@ public class UserAttributeEditor extends PropertyEditorSupport {
setValue(null);
} else {
String[] tokens = StringUtils.commaDelimitedListToStringArray(s);
UserAttributeDefinition userAttrib = new UserAttributeDefinition();
UserAttribute userAttrib = new UserAttribute();
for (int i = 0; i < tokens.length; i++) {
String currentToken = tokens[i];

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -91,8 +91,7 @@ public class UserMapEditor extends PropertyEditorSupport {
// Convert value to a password, enabled setting, and list of granted authorities
configAttribEd.setAsText(value);
UserAttributeDefinition attr = (UserAttributeDefinition) configAttribEd
.getValue();
UserAttribute attr = (UserAttribute) configAttribEd.getValue();
// Make a user object, assuming the properties were properly provided
if (attr != null) {

View File

@ -0,0 +1,67 @@
/* 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;
import junit.framework.TestCase;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
/**
* Tests {@link net.sf.acegisecurity.AuthenticationTrustResolverImpl}.
*
* @author Ben Alex
* @version $Id$
*/
public class AuthenticationTrustResolverImplTests extends TestCase {
//~ Constructors ===========================================================
public AuthenticationTrustResolverImplTests() {
super();
}
public AuthenticationTrustResolverImplTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(AuthenticationTrustResolverImplTests.class);
}
public void testCorrectOperationIsAnonymous() {
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
assertTrue(trustResolver.isAnonymous(
new AnonymousAuthenticationToken("ignored", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")})));
assertFalse(trustResolver.isAnonymous(
new TestingAuthenticationToken("ignored", "ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("ignored")})));
}
public void testGettersSetters() {
AuthenticationTrustResolverImpl trustResolver = new AuthenticationTrustResolverImpl();
assertEquals(AnonymousAuthenticationToken.class,
trustResolver.getAnonymousClass());
trustResolver.setAnonymousClass(String.class);
assertEquals(String.class, trustResolver.getAnonymousClass());
assertNull(trustResolver.getRememberMeClass());
}
}

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,17 @@ import junit.framework.TestCase;
import net.sf.acegisecurity.AccessDeniedException;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.MockAuthenticationEntryPoint;
import net.sf.acegisecurity.MockHttpServletRequest;
import net.sf.acegisecurity.MockHttpServletResponse;
import net.sf.acegisecurity.MockHttpSession;
import net.sf.acegisecurity.MockPortResolver;
import net.sf.acegisecurity.context.ContextHolder;
import net.sf.acegisecurity.context.security.SecureContext;
import net.sf.acegisecurity.context.security.SecureContextImpl;
import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
import java.io.IOException;
@ -62,8 +68,47 @@ public class SecurityEnforcementFilterTests extends TestCase {
junit.textui.TestRunner.run(SecurityEnforcementFilterTests.class);
}
public void testAccessDeniedWhenAccessDeniedException()
throws Exception {
public void testAccessDeniedWhenAnonymous() throws Exception {
// Setup our HTTP request
HttpSession session = new MockHttpSession();
MockHttpServletRequest request = new MockHttpServletRequest(null,
session);
request.setServletPath("/secure/page.html");
request.setServerPort(80);
request.setScheme("http");
request.setServerName("www.example.com");
request.setContextPath("/mycontext");
request.setRequestURL(
"http://www.example.com/mycontext/secure/page.html");
// Setup our expectation that the filter chain will not be invoked, as access is denied
MockFilterChain chain = new MockFilterChain(false);
// Setup the FilterSecurityInterceptor thrown an access denied exception
MockFilterSecurityInterceptor interceptor = new MockFilterSecurityInterceptor(true,
false);
// Setup ContextHolder, as filter needs to check if user is anonymous
SecureContext sc = new SecureContextImpl();
sc.setAuthentication(new AnonymousAuthenticationToken("ignored",
"ignored",
new GrantedAuthority[] {new GrantedAuthorityImpl("IGNORED")}));
ContextHolder.setContext(sc);
// Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor);
filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
"/login.jsp"));
MockHttpServletResponse response = new MockHttpServletResponse();
filter.doFilter(request, response, chain);
assertEquals("/mycontext/login.jsp", response.getRedirect());
assertEquals("http://www.example.com/mycontext/secure/page.html",
request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY));
}
public void testAccessDeniedWhenNonAnonymous() throws Exception {
// Setup our HTTP request
HttpSession session = new MockHttpSession();
MockHttpServletRequest request = new MockHttpServletRequest(null,
@ -77,6 +122,11 @@ public class SecurityEnforcementFilterTests extends TestCase {
MockFilterSecurityInterceptor interceptor = new MockFilterSecurityInterceptor(true,
false);
// Setup ContextHolder, as filter needs to check if user is anonymous
SecureContext sc = new SecureContextImpl();
sc.setAuthentication(null);
ContextHolder.setContext(sc);
// Test
SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
filter.setFilterSecurityInterceptor(interceptor);
@ -281,6 +331,11 @@ public class SecurityEnforcementFilterTests extends TestCase {
assertTrue(true);
}
protected void tearDown() throws Exception {
super.tearDown();
ContextHolder.setContext(null);
}
//~ Inner Classes ==========================================================
private class MockFilterChain implements FilterChain {

View File

@ -0,0 +1,122 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.anonymous;
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 AnonymousAuthenticationProvider}.
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousAuthenticationProviderTests extends TestCase {
//~ Constructors ===========================================================
public AnonymousAuthenticationProviderTests() {
super();
}
public AnonymousAuthenticationProviderTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(AnonymousAuthenticationProviderTests.class);
}
public void testDetectsAnInvalidKey() throws Exception {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
aap.setKey("qwerty");
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("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 AnonymousAuthenticationToken does not contain the expected key",
expected.getMessage());
}
}
public void testDetectsMissingKey() throws Exception {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
try {
aap.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testGettersSetters() throws Exception {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
aap.setKey("qwerty");
aap.afterPropertiesSet();
assertEquals("qwerty", aap.getKey());
}
public void testIgnoresClassesItDoesNotSupport() throws Exception {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
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 {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
aap.setKey("qwerty");
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("qwerty",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
Authentication result = aap.authenticate(token);
assertEquals(result, token);
}
public void testSupports() {
AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider();
assertTrue(aap.supports(AnonymousAuthenticationToken.class));
assertFalse(aap.supports(TestingAuthenticationToken.class));
}
}

View File

@ -0,0 +1,190 @@
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sf.acegisecurity.providers.anonymous;
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 AnonymousAuthenticationToken}.
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousAuthenticationTokenTests extends TestCase {
//~ Constructors ===========================================================
public AnonymousAuthenticationTokenTests() {
super();
}
public AnonymousAuthenticationTokenTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public final void setUp() throws Exception {
super.setUp();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(AnonymousAuthenticationTokenTests.class);
}
public void testConstructorRejectsNulls() {
try {
new AnonymousAuthenticationToken(null, "Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new AnonymousAuthenticationToken("key", null,
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new AnonymousAuthenticationToken("key", "Test", null);
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new AnonymousAuthenticationToken("key", "Test",
new GrantedAuthority[] {null});
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
try {
new AnonymousAuthenticationToken("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");
AnonymousAuthenticationToken token1 = new AnonymousAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
AnonymousAuthenticationToken token2 = new AnonymousAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertEquals(token1, token2);
}
public void testGetters() {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("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 AnonymousAuthenticationToken();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testNotEqualsDueToAbstractParentEqualsCheck() {
AnonymousAuthenticationToken token1 = new AnonymousAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
AnonymousAuthenticationToken token2 = new AnonymousAuthenticationToken("key",
"DIFFERENT_PRINCIPAL",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertFalse(token1.equals(token2));
}
public void testNotEqualsDueToDifferentAuthenticationClass() {
AnonymousAuthenticationToken token1 = new AnonymousAuthenticationToken("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() {
AnonymousAuthenticationToken token1 = new AnonymousAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
AnonymousAuthenticationToken token2 = new AnonymousAuthenticationToken("DIFFERENT_KEY",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertFalse(token1.equals(token2));
}
public void testSetAuthenticatedIgnored() {
AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("key",
"Test",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
"ROLE_TWO")});
assertTrue(token.isAuthenticated());
token.setAuthenticated(false); // ignored
assertTrue(token.isAuthenticated());
}
}

View File

@ -0,0 +1,200 @@
/* 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.anonymous;
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;
import net.sf.acegisecurity.providers.dao.memory.UserAttribute;
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;
/**
* Tests {@link AnonymousProcessingFilter}.
*
* @author Ben Alex
* @version $Id$
*/
public class AnonymousProcessingFilterTests extends TestCase {
//~ Constructors ===========================================================
public AnonymousProcessingFilterTests() {
super();
}
public AnonymousProcessingFilterTests(String arg0) {
super(arg0);
}
//~ Methods ================================================================
public static void main(String[] args) {
junit.textui.TestRunner.run(AnonymousProcessingFilterTests.class);
}
public void testDetectsMissingKey() throws Exception {
UserAttribute user = new UserAttribute();
user.setPassword("anonymousUsername");
user.addAuthority(new GrantedAuthorityImpl("ROLE_ANONYMOUS"));
AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setUserAttribute(user);
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testDetectsUserAttribute() throws Exception {
AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty");
try {
filter.afterPropertiesSet();
fail("Should have thrown IllegalArgumentException");
} catch (IllegalArgumentException expected) {
assertTrue(true);
}
}
public void testGettersSetters() throws Exception {
UserAttribute user = new UserAttribute();
user.setPassword("anonymousUsername");
user.addAuthority(new GrantedAuthorityImpl("ROLE_ANONYMOUS"));
AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty");
filter.setUserAttribute(user);
filter.afterPropertiesSet();
assertEquals("qwerty", filter.getKey());
assertEquals(user, filter.getUserAttribute());
}
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
UserAttribute user = new UserAttribute();
user.setPassword("anonymousUsername");
user.addAuthority(new GrantedAuthorityImpl("ROLE_ANONYMOUS"));
AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty");
filter.setUserAttribute(user);
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 {
UserAttribute user = new UserAttribute();
user.setPassword("anonymousUsername");
user.addAuthority(new GrantedAuthorityImpl("ROLE_ANONYMOUS"));
AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty");
filter.setUserAttribute(user);
filter.afterPropertiesSet();
executeFilterInContainerSimulator(new MockFilterConfig(), filter,
new MockHttpServletRequest("x"), new MockHttpServletResponse(),
new MockFilterChain(true));
Authentication auth = SecureContextUtils.getSecureContext()
.getAuthentication();
assertEquals("anonymousUsername", auth.getPrincipal());
assertEquals(new GrantedAuthorityImpl("ROLE_ANONYMOUS"),
auth.getAuthorities()[0]);
}
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");
}
}
}
}

View File

@ -1,4 +1,4 @@
/* Copyright 2004 Acegi Technology Pty Limited
/* Copyright 2004, 2005 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,8 +19,7 @@ import junit.framework.TestCase;
/**
* Tests {@link UserAttributeEditor} and associated {@link
* UserAttributeDefinition}.
* Tests {@link UserAttributeEditor} and associated {@link UserAttribute}.
*
* @author Ben Alex
* @version $Id$
@ -50,8 +49,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("password,ROLE_ONE,ROLE_TWO");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user.isValid());
assertTrue(user.isEnabled()); // default
assertEquals("password", user.getPassword());
@ -64,8 +62,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("password,disabled,ROLE_ONE,ROLE_TWO");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user.isValid());
assertTrue(!user.isEnabled());
assertEquals("password", user.getPassword());
@ -78,8 +75,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user == null);
}
@ -87,8 +83,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("password,ROLE_ONE,enabled,ROLE_TWO");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user.isValid());
assertTrue(user.isEnabled());
assertEquals("password", user.getPassword());
@ -101,8 +96,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("MALFORMED_STRING");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user == null);
}
@ -110,8 +104,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("disabled");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user == null);
}
@ -119,8 +112,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText("password,enabled");
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user == null);
}
@ -128,8 +120,7 @@ public class UserAttributeEditorTests extends TestCase {
UserAttributeEditor editor = new UserAttributeEditor();
editor.setAsText(null);
UserAttributeDefinition user = (UserAttributeDefinition) editor
.getValue();
UserAttribute user = (UserAttribute) editor.getValue();
assertTrue(user == null);
}
}

View File

@ -2485,7 +2485,7 @@ public boolean supports(Class clazz);</programlisting></para>
<para><programlisting>base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nounce token
key: A private key to prevent modification of the nonce token
</programlisting></para>
<para>The <literal>DigestProcessingFilterEntryPoint</literal> has a
@ -2548,7 +2548,7 @@ key: A private key to prevent modification of the nounce token
<para>The configured <literal>AuthenticationDao</literal> is needed
because <literal>DigestProcessingFilter</literal> must have direct
access to the clear text password of a user. Digest Authentication
will NOT work if you are using encoded passwords ni your DAO. The DAO
will NOT work if you are using encoded passwords in your DAO. The DAO
collaborator, along with the <literal>UserCache</literal>, are
typically shared directly with a
<literal>DaoAuthenticationProvider</literal>. The
@ -2556,7 +2556,7 @@ key: A private key to prevent modification of the nounce token
<literal>DigestProcessingFilterEntryPoint</literal>, so that
<literal>DigestProcessingFilter</literal> can obtain the correct
<literal>realmName</literal> and <literal>key</literal> for digest
calculations. </para>
calculations.</para>
<para>Like <literal>BasicAuthenticationFilter</literal>, if
authentication is successful an <literal>Authentication</literal>
@ -2580,6 +2580,95 @@ key: A private key to prevent modification of the nounce token
does comply with the minimum standards of this RFC.</para>
</sect2>
<sect2 id="security-ui-anonymous">
<title>Anonymous Authentication</title>
<para>Particularly in the case of web request URI security, sometimes
it is more convenient to assign configuration attributes against every
possible secure object invocation. Put differently, sometimes it is
nice to say <literal>ROLE_SOMETHING</literal> is required by default
and only allow certain exceptions to this rule, such as for login,
logout and home pages of an application. There are also other
situations where anonymous authentication would be desired, such as
when an auditing interceptor queries the
<literal>ContextHolder</literal> to identify which principal was
responsible for a given operation. Such classes can be authored with
more robustness if they know the <literal>ContextHolder</literal>
always contains an <literal>Authentication</literal> object, and never
<literal>null</literal>.</para>
<para>Acegi Security provides three classes that together provide an
anoymous authentication feature.
<literal>AnonymousAuthenticationToken</literal> is an implementation
of <literal>Authentication</literal>, and stores the
<literal>GrantedAuthority</literal>[]s which apply to the anonymous
principal. There is a corresponding
<literal>AnonymousAuthenticationProvider</literal>, which is chained
into the <literal>ProviderManager</literal> so that
<literal>AnonymousAuthenticationTokens</literal> are accepted.
Finally, there is an AnonymousProcessingFilter, which is chained after
the normal authentication mechanisms and automatically add an
<literal>AnonymousAuthenticationToken</literal> to the
<literal>ContextHolder</literal> if there is no existing
<literal>Authentication</literal> held there. The definition of the
filter and authentication provider appears as follows:</para>
<para><programlisting>&lt;bean id="anonymousProcessingFilter" class="net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter"&gt;
&lt;property name="key"&gt;&lt;value&gt;foobar&lt;/value&gt;&lt;/property&gt;
&lt;property name="userAttribute"&gt;&lt;value&gt;anonymousUser,ROLE_ANONYMOUS&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;
&lt;bean id="anonymousAuthenticationProvider" class="net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider"&gt;
&lt;property name="key"&gt;&lt;value&gt;foobar&lt;/value&gt;&lt;/property&gt;
&lt;/bean&gt;</programlisting></para>
<para>The <literal>key</literal> is shared between the filter and
authentication provider, so that tokens created by the former are
accepted by the latter. The <literal>userAttribute</literal> is
expressed in the form of
<literal>usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority]</literal>.
This is the same syntax as used after the equals sign for
<literal>InMemoryDaoImpl</literal>'s <literal>userMap</literal>
property.</para>
<para>As explained earlier, the benefit of anonymous authentication is
that all URI patterns can have security applied to them. For
example:</para>
<para><programlisting>&lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
&lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
&lt;property name="accessDecisionManager"&gt;&lt;ref local="httpRequestAccessDecisionManager"/&gt;&lt;/property&gt;
&lt;property name="objectDefinitionSource"&gt;
&lt;value&gt;
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
/**=ROLE_USER
&lt;/value&gt;
&lt;/property&gt;
&lt;/bean&gt;</programlisting>Rounding out the anonymous authentication
discussion is the <literal>AuthenticationTrustResolver</literal>
interface, with its corresponding
<literal>AuthenticationTrustResolverImpl</literal> implementation.
This interface provides an
<literal>isAnonymous(Authentication)</literal> method, which allows
interested classes to take into account this special type of
authentication status. The
<literal>SecurityEnforcementFilter</literal> uses this interface in
processing <literal>AccessDeniedException</literal>s. If an
<literal>AccessDeniedException</literal> is thrown, and the
authentication is of an anonymous type, instead of throwing a 403
(forbidden) response, the filter will instead commence the
<literal>AuthenticationEntryPoint</literal> so the principal can
authenticate properly. This is a necessary distinction, otherwise
principals would always be deemed "authenticated" and never be given
an opportunity to login via form, basic, digest or some other normal
authentication mechanism.</para>
</sect2>
<sect2 id="security-ui-well-known">
<title>Well-Known Locations</title>
@ -4393,6 +4482,13 @@ INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);</programlisting></para>
container</para>
</listitem>
<listitem>
<para><literal>AnonymousProcessingFilter</literal>, so that if no
earlier authentication processing mechanism updated the
<literal>ContextHolder</literal>, an anonymous
<literal>Authentication</literal> object will be put there</para>
</listitem>
<listitem>
<para><literal>SecurityEnforcementFilter</literal>, to protect web
URIs and catch any Acegi Security exceptions so that an

View File

@ -21,7 +21,7 @@
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,securityEnforcementFilter
/**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
</value>
</property>
</bean>
@ -32,6 +32,7 @@
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
<ref local="anonymousAuthenticationProvider"/>
</list>
</property>
</bean>
@ -75,6 +76,15 @@
<property name="realmName"><value>Contacts Realm</value></property>
</bean>
<bean id="anonymousProcessingFilter" class="net.sf.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
<property name="key"><value>foobar</value></property>
<property name="userAttribute"><value>anonymousUser,ROLE_ANONYMOUS</value></property>
</bean>
<bean id="anonymousAuthenticationProvider" class="net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
<property name="key"><value>foobar</value></property>
</bean>
<bean id="httpSessionContextIntegrationFilter" class="net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter">
<property name="context"><value>net.sf.acegisecurity.context.security.SecureContextImpl</value></property>
</bean>
@ -146,33 +156,14 @@
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A/secure/super.*\Z=ROLE_WE_DONT_HAVE
\A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_USER
PATTERN_TYPE_APACHE_ANT
/index.jsp=ROLE_ANONYMOUS,ROLE_USER
/hello.htm=ROLE_ANONYMOUS,ROLE_USER
/logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
/acegilogin.jsp=ROLE_ANONYMOUS,ROLE_USER
/**=ROLE_USER
</value>
</property>
</bean>
<!-- BASIC Regular Expression Syntax (for beginners):
\A means the start of the string (ie the beginning of the URL)
\Z means the end of the string (ie the end of the URL)
. means any single character
* means null or any number of repetitions of the last expression (so .* means zero or more characters)
Some examples:
Expression: \A/my/directory/.*\Z
Would match: /my/directory/
/my/directory/hello.html
Expression: \A/.*\Z
Would match: /hello.html
/
Expression: \A/.*/secret.html\Z
Would match: /some/directory/secret.html
/another/secret.html
Not match: /anothersecret.html (missing required /)
-->
</beans>