From 693ac5a24ac6800323b4e8f1a9a3968d201c84a6 Mon Sep 17 00:00:00 2001
From: Ben Alex
Date: Wed, 23 Feb 2005 06:09:56 +0000
Subject: [PATCH] Anonymous principal support. As requested by the community at
various times, including in
http://forum.springframework.org/viewtopic.php?t=1925.
---
.../acegisecurity/AccessDecisionManager.java | 10 +-
.../AuthenticationException.java | 15 +-
.../AuthenticationTrustResolver.java | 66 ++++++
.../AuthenticationTrustResolverImpl.java | 77 +++++++
.../InsufficientAuthenticationException.java | 58 +++++
.../web/SecurityEnforcementFilter.java | 148 +++++++------
.../AnonymousAuthenticationProvider.java | 86 ++++++++
.../AnonymousAuthenticationToken.java | 133 ++++++++++++
.../anonymous/AnonymousProcessingFilter.java | 169 +++++++++++++++
.../providers/anonymous/package.html | 7 +
...buteDefinition.java => UserAttribute.java} | 6 +-
.../memory/UserAttributeEditor.java | 8 +-
.../userdetails/memory/UserMapEditor.java | 5 +-
.../AuthenticationTrustResolverImplTests.java | 67 ++++++
.../web/SecurityEnforcementFilterTests.java | 61 +++++-
.../AnonymousAuthenticationProviderTests.java | 122 +++++++++++
.../AnonymousAuthenticationTokenTests.java | 190 +++++++++++++++++
.../AnonymousProcessingFilterTests.java | 200 ++++++++++++++++++
.../dao/memory/UserAttributeEditorTests.java | 29 +--
doc/docbook/acegi.xml | 102 ++++++++-
.../applicationContext-acegi-security.xml | 43 ++--
21 files changed, 1467 insertions(+), 135 deletions(-)
create mode 100644 core/src/main/java/org/acegisecurity/AuthenticationTrustResolver.java
create mode 100644 core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java
create mode 100644 core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java
create mode 100644 core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java
create mode 100644 core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java
create mode 100644 core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java
create mode 100644 core/src/main/java/org/acegisecurity/providers/anonymous/package.html
rename core/src/main/java/org/acegisecurity/userdetails/memory/{UserAttributeDefinition.java => UserAttribute.java} (94%)
create mode 100644 core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java
create mode 100644 core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java
create mode 100644 core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationTokenTests.java
create mode 100644 core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilterTests.java
diff --git a/core/src/main/java/org/acegisecurity/AccessDecisionManager.java b/core/src/main/java/org/acegisecurity/AccessDecisionManager.java
index bb0abd873c..e53f60ade9 100644
--- a/core/src/main/java/org/acegisecurity/AccessDecisionManager.java
+++ b/core/src/main/java/org/acegisecurity/AccessDecisionManager.java
@@ -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 AccessDecisionManager
is able to
diff --git a/core/src/main/java/org/acegisecurity/AuthenticationException.java b/core/src/main/java/org/acegisecurity/AuthenticationException.java
index 3476d5b94e..d82d053662 100644
--- a/core/src/main/java/org/acegisecurity/AuthenticationException.java
+++ b/core/src/main/java/org/acegisecurity/AuthenticationException.java
@@ -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;
+ }
}
diff --git a/core/src/main/java/org/acegisecurity/AuthenticationTrustResolver.java b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolver.java
new file mode 100644
index 0000000000..34f26efe23
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolver.java
@@ -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 Authentication
tokens
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AuthenticationTrustResolver {
+ //~ Methods ================================================================
+
+ /**
+ * Indicates whether the passed Authentication
token
+ * represents an anonymous user. Typically the framework will call this
+ * method if it is trying to decide whether an
+ * AccessDeniedException
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 Authentication
was merely
+ * anonymous).
+ *
+ * @param authentication to test (may be null
in which case
+ * the method will always return false
)
+ *
+ * @return true
the passed authentication token represented an
+ * anonymous principal, false
otherwise
+ */
+ public boolean isAnonymous(Authentication authentication);
+
+ /**
+ * Indicates whether the passed Authentication
token
+ * represents user that has been remembered (ie not a user that has been
+ * fully authenticated).
+ *
+ *
+ * No part of the framework uses this method, as it is a weak
+ * definition of trust levels. The method is provided simply to assist
+ * with custom AccessDecisionVoter
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.
+ *
+ *
+ * @param authentication to test (may be null
in which case
+ * the method will always return false
)
+ *
+ * @return true
the passed authentication token represented a
+ * principal authenticated using a remember-me token,
+ * false
otherwise
+ */
+ public boolean isRememberMe(Authentication authentication);
+}
diff --git a/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java
new file mode 100644
index 0000000000..c043e268aa
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/AuthenticationTrustResolverImpl.java
@@ -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}.
+ *
+ *
+ * Makes trust decisions based on whether the passed
+ * Authentication
is an instance of a defined class.
+ *
+ *
+ *
+ * If {@link #anonymousClass} or {@link #rememberMeClass} is null
,
+ * the corresponding method will always return false
.
+ *
+ *
+ * @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;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java b/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java
new file mode 100644
index 0000000000..aac2abb6c7
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java
@@ -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.
+ *
+ *
+ * {{@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 AuthenticationEntryPoint
to be called, allowing the
+ * principal to authenticate with a stronger level of authentication. }
+ *
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class InsufficientAuthenticationException extends AuthenticationException {
+ //~ Constructors ===========================================================
+
+ /**
+ * Constructs an InsufficientAuthenticationException
with the
+ * specified message.
+ *
+ * @param msg the detail message
+ */
+ public InsufficientAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an InsufficientAuthenticationException
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);
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java b/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java
index de19a2b6d6..23d610fe78 100644
--- a/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java
+++ b/core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java
@@ -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;
*
*
*
- * If an {@link AccessDeniedException} is detected, the filter will respond
- * with a HttpServletResponse.SC_FORBIDDEN
(403 error). In
- * addition, the AccessDeniedException
itself will be placed in
- * the HttpSession
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 authenticationEntryPoint
will be launched. If they
+ * are not an anonymous user, the filter will respond with a
+ * HttpServletResponse.SC_FORBIDDEN
(403 error). In addition,
+ * the AccessDeniedException
itself will be placed in the
+ * HttpSession
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);
+ }
}
diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java
new file mode 100644
index 0000000000..137f4d8507
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProvider.java
@@ -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.
+ *
+ *
+ * To be successfully validated, the {@link
+ * net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken#getKeyHash()}
+ * must match this class' {@link #getKey()}.
+ *
+ *
+ * @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));
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java
new file mode 100644
index 0000000000..2f52920f58
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java
@@ -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 Authentication
.
+ *
+ * @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 UserDetails
)
+ * @param authorities the authorities granted to the principal
+ *
+ * @throws IllegalArgumentException if a null
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 true
).
+ *
+ * @param isAuthenticated ignored
+ */
+ public void setAuthenticated(boolean isAuthenticated) {
+ // ignored
+ }
+
+ /**
+ * Always returns true
.
+ *
+ * @return true
+ */
+ public boolean isAuthenticated() {
+ return true;
+ }
+
+ public GrantedAuthority[] getAuthorities() {
+ return this.authorities;
+ }
+
+ /**
+ * Always returns an empty String
+ *
+ * @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;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java
new file mode 100644
index 0000000000..6542dcebdb
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java
@@ -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 Authentication
object in the
+ * ContextHolder
, and populates it with one if needed.
+ *
+ *
+ *
+ *
+ * In summary, this filter is responsible for processing any request that has a
+ * HTTP request header of Authorization
with an authentication
+ * scheme of Basic
and a Base64-encoded
+ * username:password
token. For example, to authenticate user
+ * "Aladdin" with password "open sesame" the following header would be
+ * presented:
+ *
+ *
+ *
+ * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
.
+ *
+ *
+ *
+ * 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).
+ *
+ *
+ *
+ * If authentication is successful, the resulting {@link Authentication} object
+ * will be placed into the ContextHolder
.
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * 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}.
+ *
+ *
+ *
+ * Do not use this class directly. Instead configure
+ * web.xml
to use the {@link
+ * net.sf.acegisecurity.util.FilterToBeanProxy}.
+ *
+ *
+ * @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());
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/package.html b/core/src/main/java/org/acegisecurity/providers/anonymous/package.html
new file mode 100644
index 0000000000..6a9d20f197
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/anonymous/package.html
@@ -0,0 +1,7 @@
+
+
+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.
+
+
diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeDefinition.java b/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttribute.java
similarity index 94%
rename from core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeDefinition.java
rename to core/src/main/java/org/acegisecurity/userdetails/memory/UserAttribute.java
index ec102aeb56..e42ec4be8f 100644
--- a/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeDefinition.java
+++ b/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttribute.java
@@ -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();
}
diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeEditor.java b/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeEditor.java
index 19fa96d0b0..b6db0615d8 100644
--- a/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeEditor.java
+++ b/core/src/main/java/org/acegisecurity/userdetails/memory/UserAttributeEditor.java
@@ -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];
diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java b/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java
index cc402f9924..b7a97f7149 100644
--- a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java
+++ b/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java
@@ -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) {
diff --git a/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java b/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java
new file mode 100644
index 0000000000..c1fc48c938
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/AuthenticationTrustResolverImplTests.java
@@ -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());
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java b/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java
index dd2a2e6539..61ac89da34 100644
--- a/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java
+++ b/core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java
@@ -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 {
diff --git a/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java
new file mode 100644
index 0000000000..4dd3efab71
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationProviderTests.java
@@ -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));
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationTokenTests.java b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationTokenTests.java
new file mode 100644
index 0000000000..a631756173
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationTokenTests.java
@@ -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());
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilterTests.java
new file mode 100644
index 0000000000..25234b89db
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilterTests.java
@@ -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");
+ }
+ }
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/dao/memory/UserAttributeEditorTests.java b/core/src/test/java/org/acegisecurity/providers/dao/memory/UserAttributeEditorTests.java
index ca01016c4a..aa5b1b5f11 100644
--- a/core/src/test/java/org/acegisecurity/providers/dao/memory/UserAttributeEditorTests.java
+++ b/core/src/test/java/org/acegisecurity/providers/dao/memory/UserAttributeEditorTests.java
@@ -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);
}
}
diff --git a/doc/docbook/acegi.xml b/doc/docbook/acegi.xml
index 6b4d9522e8..9d214c11d3 100644
--- a/doc/docbook/acegi.xml
+++ b/doc/docbook/acegi.xml
@@ -2485,7 +2485,7 @@ public boolean supports(Class clazz);
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
The DigestProcessingFilterEntryPoint has a
@@ -2548,7 +2548,7 @@ key: A private key to prevent modification of the nounce token
The configured AuthenticationDao is needed
because DigestProcessingFilter 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 UserCache, are
typically shared directly with a
DaoAuthenticationProvider. The
@@ -2556,7 +2556,7 @@ key: A private key to prevent modification of the nounce token
DigestProcessingFilterEntryPoint, so that
DigestProcessingFilter can obtain the correct
realmName and key for digest
- calculations.
+ calculations.
Like BasicAuthenticationFilter, if
authentication is successful an Authentication
@@ -2580,6 +2580,95 @@ key: A private key to prevent modification of the nounce token
does comply with the minimum standards of this RFC.
+
+ Anonymous Authentication
+
+ 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 ROLE_SOMETHING 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
+ ContextHolder to identify which principal was
+ responsible for a given operation. Such classes can be authored with
+ more robustness if they know the ContextHolder
+ always contains an Authentication object, and never
+ null.
+
+ Acegi Security provides three classes that together provide an
+ anoymous authentication feature.
+ AnonymousAuthenticationToken is an implementation
+ of Authentication, and stores the
+ GrantedAuthority[]s which apply to the anonymous
+ principal. There is a corresponding
+ AnonymousAuthenticationProvider, which is chained
+ into the ProviderManager so that
+ AnonymousAuthenticationTokens are accepted.
+ Finally, there is an AnonymousProcessingFilter, which is chained after
+ the normal authentication mechanisms and automatically add an
+ AnonymousAuthenticationToken to the
+ ContextHolder if there is no existing
+ Authentication held there. The definition of the
+ filter and authentication provider appears as follows:
+
+ <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>
+
+ The key is shared between the filter and
+ authentication provider, so that tokens created by the former are
+ accepted by the latter. The userAttribute is
+ expressed in the form of
+ usernameInTheAuthenticationToken,grantedAuthority[,grantedAuthority].
+ This is the same syntax as used after the equals sign for
+ InMemoryDaoImpl's userMap
+ property.
+
+ As explained earlier, the benefit of anonymous authentication is
+ that all URI patterns can have security applied to them. For
+ example:
+
+ <bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor">
+ <property name="authenticationManager"><ref bean="authenticationManager"/></property>
+ <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
+ <property name="objectDefinitionSource">
+ <value>
+ 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
+ </value>
+ </property>
+</bean>Rounding out the anonymous authentication
+ discussion is the AuthenticationTrustResolver
+ interface, with its corresponding
+ AuthenticationTrustResolverImpl implementation.
+ This interface provides an
+ isAnonymous(Authentication) method, which allows
+ interested classes to take into account this special type of
+ authentication status. The
+ SecurityEnforcementFilter uses this interface in
+ processing AccessDeniedExceptions. If an
+ AccessDeniedException is thrown, and the
+ authentication is of an anonymous type, instead of throwing a 403
+ (forbidden) response, the filter will instead commence the
+ AuthenticationEntryPoint 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.
+
+
Well-Known Locations
@@ -4393,6 +4482,13 @@ INSERT INTO acl_permission VALUES (null, 6, 'scott', 1);
container
+
+ AnonymousProcessingFilter, so that if no
+ earlier authentication processing mechanism updated the
+ ContextHolder, an anonymous
+ Authentication object will be put there
+
+
SecurityEnforcementFilter, to protect web
URIs and catch any Acegi Security exceptions so that an
diff --git a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml
index 95ca7effc9..3c523a3bcd 100644
--- a/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml
+++ b/samples/contacts/src/main/webapp/filter/WEB-INF/applicationContext-acegi-security.xml
@@ -21,7 +21,7 @@
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
- /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,securityEnforcementFilter
+ /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,basicProcessingFilter,anonymousProcessingFilter,securityEnforcementFilter
@@ -32,6 +32,7 @@
+
@@ -75,6 +76,15 @@
Contacts Realm
+
+ foobar
+ anonymousUser,ROLE_ANONYMOUS
+
+
+
+ foobar
+
+
net.sf.acegisecurity.context.security.SecureContextImpl
@@ -146,33 +156,14 @@
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
-
-