浏览代码

SEC-271: added Ordered interface to AcessDecisionVoters

Vishal Puri 18 年之前
父节点
当前提交
0e46e5307c

+ 496 - 388
core/src/main/java/org/acegisecurity/intercept/AbstractSecurityInterceptor.java

@@ -39,8 +39,11 @@ import org.acegisecurity.runas.NullRunAsManager;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
 
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationEventPublisher;
 import org.springframework.context.ApplicationEventPublisherAware;
@@ -52,399 +55,504 @@ import org.springframework.util.Assert;
 
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.Set;
 
-
 /**
- * Abstract class that implements security interception for secure objects.<p>The
- * <code>AbstractSecurityInterceptor</code> will ensure the proper startup configuration of the security interceptor.
- * It will also implement the proper handling of secure object invocations, being:
- *  <ol>
- *      <li>Obtain the {@link Authentication} object from the {@link SecurityContextHolder}.</li>
- *      <li>Determine if the request relates to a secured or public invocation by looking up the secure object
- *      request against the {@link ObjectDefinitionSource}.</li>
- *      <li>For an invocation that is secured (there is a <code>ConfigAttributeDefinition</code> for the secure
- *      object invocation):
- *      <ol type="a">
- *          <li>If either the {@link org.acegisecurity.Authentication#isAuthenticated()} returns
- *          <code>false</code>, or the {@link #alwaysReauthenticate} is <code>true</code>,  authenticate the request
- *          against the configured {@link AuthenticationManager}. When authenticated, replace the
- *          <code>Authentication</code> object on the <code>SecurityContextHolder</code> with the returned value.</li>
- *          <li>Authorize the request against the configured {@link AccessDecisionManager}.</li>
- *          <li>Perform any run-as replacement via the configured {@link RunAsManager}.</li>
- *          <li>Pass control back to the concrete subclass, which will actually proceed with executing the
- *          object. A {@link InterceptorStatusToken} is returned so that after the subclass has finished proceeding
- *          with  execution of the object, its finally clause can ensure the <code>AbstractSecurityInterceptor</code>
- *          is re-called and tidies up correctly.</li>
- *          <li>The concrete subclass will re-call the <code>AbstractSecurityInterceptor</code> via the
- *          {@link #afterInvocation(InterceptorStatusToken, Object)} method.</li>
- *          <li>If the <code>RunAsManager</code> replaced the <code>Authentication</code> object, return
- *          the <code>SecurityContextHolder</code> to the object that existed after the call to
- *          <code>AuthenticationManager</code>.</li>
- *          <li>If an <code>AfterInvocationManager</code> is defined, invoke the invocation manager and
- *          allow it to replace the object due to be returned to the caller.</li>
- *      </ol>
- *      </li>
- *      <li>For an invocation that is public (there is no <code>ConfigAttributeDefinition</code> for the secure
- *      object invocation):
- *      <ol type="a">
- *          <li>As described above, the concrete subclass will be returned an
- *          <code>InterceptorStatusToken</code> which is subsequently re-presented to the
- *          <code>AbstractSecurityInterceptor</code> after the secure object has been executed. The
- *          <code>AbstractSecurityInterceptor</code> will take no further action when its {@link
- *          #afterInvocation(InterceptorStatusToken, Object)} is called.</li>
- *      </ol>
- *      </li>
- *      <li>Control again returns to the concrete subclass, along with the <code>Object</code> that should be
- *      returned to the caller.  The subclass will then return that  result or exception to the original caller.</li>
- *  </ol>
- *  </p>
- *
+ * Abstract class that implements security interception for secure objects.
+ * <p>
+ * The <code>AbstractSecurityInterceptor</code> will ensure the proper startup
+ * configuration of the security interceptor. It will also implement the proper
+ * handling of secure object invocations, being:
+ * <ol>
+ * <li>Obtain the {@link Authentication} object from the
+ * {@link SecurityContextHolder}.</li>
+ * <li>Determine if the request relates to a secured or public invocation by
+ * looking up the secure object request against the
+ * {@link ObjectDefinitionSource}.</li>
+ * <li>For an invocation that is secured (there is a
+ * <code>ConfigAttributeDefinition</code> for the secure object invocation):
+ * <ol type="a">
+ * <li>If either the {@link org.acegisecurity.Authentication#isAuthenticated()}
+ * returns <code>false</code>, or the {@link #alwaysReauthenticate} is
+ * <code>true</code>, authenticate the request against the configured
+ * {@link AuthenticationManager}. When authenticated, replace the
+ * <code>Authentication</code> object on the
+ * <code>SecurityContextHolder</code> with the returned value.</li>
+ * <li>Authorize the request against the configured
+ * {@link AccessDecisionManager}.</li>
+ * <li>Perform any run-as replacement via the configured {@link RunAsManager}.</li>
+ * <li>Pass control back to the concrete subclass, which will actually proceed
+ * with executing the object. A {@link InterceptorStatusToken} is returned so
+ * that after the subclass has finished proceeding with execution of the object,
+ * its finally clause can ensure the <code>AbstractSecurityInterceptor</code>
+ * is re-called and tidies up correctly.</li>
+ * <li>The concrete subclass will re-call the
+ * <code>AbstractSecurityInterceptor</code> via the
+ * {@link #afterInvocation(InterceptorStatusToken, Object)} method.</li>
+ * <li>If the <code>RunAsManager</code> replaced the
+ * <code>Authentication</code> object, return the
+ * <code>SecurityContextHolder</code> to the object that existed after the
+ * call to <code>AuthenticationManager</code>.</li>
+ * <li>If an <code>AfterInvocationManager</code> is defined, invoke the
+ * invocation manager and allow it to replace the object due to be returned to
+ * the caller.</li>
+ * </ol>
+ * </li>
+ * <li>For an invocation that is public (there is no
+ * <code>ConfigAttributeDefinition</code> for the secure object invocation):
+ * <ol type="a">
+ * <li>As described above, the concrete subclass will be returned an
+ * <code>InterceptorStatusToken</code> which is subsequently re-presented to
+ * the <code>AbstractSecurityInterceptor</code> after the secure object has
+ * been executed. The <code>AbstractSecurityInterceptor</code> will take no
+ * further action when its {@link #afterInvocation(InterceptorStatusToken,
+ * Object)} is called.</li>
+ * </ol>
+ * </li>
+ * <li>Control again returns to the concrete subclass, along with the
+ * <code>Object</code> that should be returned to the caller. The subclass
+ * will then return that result or exception to the original caller.</li>
+ * </ol>
+ * </p>
+ * 
  * @author Ben Alex
- * @version $Id$
+ * @version $Id: AbstractSecurityInterceptor.java 1790 2007-03-30 18:27:19Z
+ * luke_t $
  */
 public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware,
-    MessageSourceAware {
-    //~ Static fields/initializers =====================================================================================
-
-    protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
-
-    //~ Instance fields ================================================================================================
-
-    private AccessDecisionManager accessDecisionManager;
-    private AfterInvocationManager afterInvocationManager;
-    private ApplicationEventPublisher eventPublisher;
-    private AuthenticationManager authenticationManager;
-    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
-    private RunAsManager runAsManager = new NullRunAsManager();
-    private boolean alwaysReauthenticate = false;
-    private boolean rejectPublicInvocations = false;
-    private boolean validateConfigAttributes = true;
-
-    //~ Methods ========================================================================================================
-
-    /**
-     * Completes the work of the <code>AbstractSecurityInterceptor</code> after the secure object invocation
-     * has been complete
-     *
-     * @param token as returned by the {@link #beforeInvocation(Object)}} method
-     * @param returnedObject any object returned from the secure object invocation (may be<code>null</code>)
-     *
-     * @return the object the secure object invocation should ultimately return to its caller (may be
-     *         <code>null</code>)
-     */
-    protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
-        if (token == null) {
-            // public object
-            return returnedObject;
-        }
-
-        if (token.isContextHolderRefreshRequired()) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Reverting to original Authentication: " + token.getAuthentication().toString());
-            }
-
-            SecurityContextHolder.getContext().setAuthentication(token.getAuthentication());
-        }
-
-        if (afterInvocationManager != null) {
-            // Attempt after invocation handling
-            try {
-                returnedObject = afterInvocationManager.decide(token.getAuthentication(), token.getSecureObject(),
-                        token.getAttr(), returnedObject);
-            } catch (AccessDeniedException accessDeniedException) {
-                AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(),
-                        token.getAttr(), token.getAuthentication(), accessDeniedException);
-                publishEvent(event);
-
-                throw accessDeniedException;
-            }
-        }
-
-        return returnedObject;
-    }
-
-    public void afterPropertiesSet() throws Exception {
-        Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
-
-        Assert.notNull(this.messages, "A message source must be set");
-        Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
-
-        Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
-
-        Assert.notNull(this.runAsManager, "A RunAsManager is required");
-
-        Assert.notNull(this.obtainObjectDefinitionSource(), "An ObjectDefinitionSource is required");
-
-        Assert.isTrue(this.obtainObjectDefinitionSource().supports(getSecureObjectClass()),
-            "ObjectDefinitionSource does not support secure object class: " + getSecureObjectClass());
-
-        Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()),
-            "RunAsManager does not support secure object class: " + getSecureObjectClass());
-
-        Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()),
-            "AccessDecisionManager does not support secure object class: " + getSecureObjectClass());
-
-        if (this.afterInvocationManager != null) {
-            Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()),
-                "AfterInvocationManager does not support secure object class: " + getSecureObjectClass());
-        }
-
-        if (this.validateConfigAttributes) {
-            Iterator iter = this.obtainObjectDefinitionSource().getConfigAttributeDefinitions();
-
-            if (iter == null) {
-                logger.warn("Could not validate configuration attributes as the MethodDefinitionSource did not return "
-                        + "a ConfigAttributeDefinition Iterator");
-                return;
-            }
-
-            Set unsupportedAttrs = new HashSet();
-
-            while (iter.hasNext()) {
-                ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter.next();
-                Iterator attributes = def.getConfigAttributes();
-
-                while (attributes.hasNext()) {
-                    ConfigAttribute attr = (ConfigAttribute) attributes.next();
-
-                    if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
-                        && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
-                        unsupportedAttrs.add(attr);
-                    }
-                }
-            }
-
-            if (unsupportedAttrs.size() != 0) {
-                throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
-            }
-
-            logger.info("Validated configuration attributes");
-        }
-    }
-
-    protected InterceptorStatusToken beforeInvocation(Object object) {
-        Assert.notNull(object, "Object was null");
-
-        if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
-            throw new IllegalArgumentException("Security invocation attempted for object "
-                + object.getClass().getName()
-                + " but AbstractSecurityInterceptor only configured to support secure objects of type: "
-                + getSecureObjectClass());
-        }
-
-        ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
-
-        if (attr == null) {
-            if(rejectPublicInvocations) {
-                throw new IllegalArgumentException(
-                      "No public invocations are allowed via this AbstractSecurityInterceptor. "
-                    + "This indicates a configuration error because the "
-                    + "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
-            }
-
-            if (logger.isDebugEnabled()) {
-                logger.debug("Public object - authentication not attempted");
-            }
-
-            publishEvent(new PublicInvocationEvent(object));
-
-            return null; // no further work post-invocation
-        }
-
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Secure object: " + object.toString() + "; ConfigAttributes: " + attr.toString());
-        }
+		MessageSourceAware, ApplicationContextAware {
+	// ~ Static fields/initializers
+	// =====================================================================================
+
+	protected static final Log logger = LogFactory.getLog(AbstractSecurityInterceptor.class);
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private AccessDecisionManager accessDecisionManager;
+
+	private AfterInvocationManager afterInvocationManager;
+
+	private ApplicationEventPublisher eventPublisher;
+
+	private AuthenticationManager authenticationManager;
+
+	protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
+
+	private RunAsManager runAsManager = new NullRunAsManager();
+
+	private boolean alwaysReauthenticate = false;
+
+	private boolean rejectPublicInvocations = false;
+
+	private boolean validateConfigAttributes = true;
+
+	private boolean isSetAuthenticationManagerInvoked = false;
+
+	private boolean isSetAccessDecisionManagerInvoked = false;
+
+	private ApplicationContext applicationContext;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	/**
+	 * Completes the work of the <code>AbstractSecurityInterceptor</code>
+	 * after the secure object invocation has been complete
+	 * 
+	 * @param token as returned by the {@link #beforeInvocation(Object)}}
+	 * method
+	 * @param returnedObject any object returned from the secure object
+	 * invocation (may be<code>null</code>)
+	 * 
+	 * @return the object the secure object invocation should ultimately return
+	 * to its caller (may be <code>null</code>)
+	 */
+	protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
+		if (token == null) {
+			// public object
+			return returnedObject;
+		}
+
+		if (token.isContextHolderRefreshRequired()) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Reverting to original Authentication: " + token.getAuthentication().toString());
+			}
+
+			SecurityContextHolder.getContext().setAuthentication(token.getAuthentication());
+		}
 
-        if (SecurityContextHolder.getContext().getAuthentication() == null) {
-            credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
-                    "An Authentication object was not found in the SecurityContext"), object, attr);
-        }
-
-        // Attempt authentication if not already authenticated, or user always wants reauthentication
-        Authentication authenticated;
-
-        if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || alwaysReauthenticate) {
-            try {
-                authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
-                                                                                             .getAuthentication());
-            } catch (AuthenticationException authenticationException) {
-                throw authenticationException;
-            }
-
-            // We don't authenticated.setAuthentication(true), because each provider should do that
-            if (logger.isDebugEnabled()) {
-                logger.debug("Successfully Authenticated: " + authenticated.toString());
-            }
-
-            SecurityContextHolder.getContext().setAuthentication(authenticated);
-        } else {
-            authenticated = SecurityContextHolder.getContext().getAuthentication();
-
-            if (logger.isDebugEnabled()) {
-                logger.debug("Previously Authenticated: " + authenticated.toString());
-            }
-        }
-
-        // Attempt authorization
-        try {
-            this.accessDecisionManager.decide(authenticated, object, attr);
-        } catch (AccessDeniedException accessDeniedException) {
-            AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated,
-                    accessDeniedException);
-            publishEvent(event);
-
-            throw accessDeniedException;
-        }
-
-        if (logger.isDebugEnabled()) {
-            logger.debug("Authorization successful");
-        }
-
-        AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated);
-        publishEvent(event);
-
-        // Attempt to run as a different user
-        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
-
-        if (runAs == null) {
-            if (logger.isDebugEnabled()) {
-                logger.debug("RunAsManager did not change Authentication object");
-            }
-
-            // no further work post-invocation
-            return new InterceptorStatusToken(authenticated, false, attr, object);
-        } else {
-            if (logger.isDebugEnabled()) {
-                logger.debug("Switching to RunAs Authentication: " + runAs.toString());
-            }
-
-            SecurityContextHolder.getContext().setAuthentication(runAs);
-
-            // revert to token.Authenticated post-invocation
-            return new InterceptorStatusToken(authenticated, true, attr, object);
-        }
-    }
-
-    /**
-     * Helper method which generates an exception containing the passed reason, and publishes an event to the
-     * application context.<p>Always throws an exception.</p>
-     *
-     * @param reason to be provided in the exception detail
-     * @param secureObject that was being called
-     * @param configAttribs that were defined for the secureObject
-     */
-    private void credentialsNotFound(String reason, Object secureObject, ConfigAttributeDefinition configAttribs) {
-        AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
-
-        AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
-                configAttribs, exception);
-        publishEvent(event);
-
-        throw exception;
-    }
-
-    public AccessDecisionManager getAccessDecisionManager() {
-        return accessDecisionManager;
-    }
-
-    public AfterInvocationManager getAfterInvocationManager() {
-        return afterInvocationManager;
-    }
-
-    public AuthenticationManager getAuthenticationManager() {
-        return this.authenticationManager;
-    }
-
-    public RunAsManager getRunAsManager() {
-        return runAsManager;
-    }
-
-    /**
-     * Indicates the type of secure objects the subclass will be presenting to the abstract parent for
-     * processing. This is used to ensure collaborators wired to the <code>AbstractSecurityInterceptor</code> all
-     * support the indicated secure object class.
-     *
-     * @return the type of secure object the subclass provides services for
-     */
-    public abstract Class getSecureObjectClass();
-
-    public boolean isAlwaysReauthenticate() {
-        return alwaysReauthenticate;
-    }
-
-    public boolean isRejectPublicInvocations() {
-        return rejectPublicInvocations;
-    }
-
-    public boolean isValidateConfigAttributes() {
-        return validateConfigAttributes;
-    }
-
-    public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
-
-    public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
-        this.accessDecisionManager = accessDecisionManager;
-    }
-
-    public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) {
-        this.afterInvocationManager = afterInvocationManager;
-    }
-
-    /**
-     * Indicates whether the <code>AbstractSecurityInterceptor</code> should ignore the {@link
-     * Authentication#isAuthenticated()} property. Defaults to <code>false</code>, meaning by default the
-     * <code>Authentication.isAuthenticated()</code> property is trusted and re-authentication will not occur if the
-     * principal has already been authenticated.
-     *
-     * @param alwaysReauthenticate <code>true</code> to force <code>AbstractSecurityInterceptor</code> to disregard the
-     *        value of <code>Authentication.isAuthenticated()</code> and always re-authenticate the request (defaults
-     *        to <code>false</code>).
-     */
-    public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
-        this.alwaysReauthenticate = alwaysReauthenticate;
-    }
-
-    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
-        this.eventPublisher = applicationEventPublisher;
-    }
-
-    public void setAuthenticationManager(AuthenticationManager newManager) {
-        this.authenticationManager = newManager;
-    }
-
-    public void setMessageSource(MessageSource messageSource) {
-        this.messages = new MessageSourceAccessor(messageSource);
-    }
-
-    /**
-     * By rejecting public invocations (and setting this property to <code>true</code>), essentially you are
-     * ensuring that every secure object invocation advised by <code>AbstractSecurityInterceptor</code> has a
-     * configuration attribute defined. This is useful to ensure a "fail safe" mode where undeclared secure objects
-     * will be rejected and configuration omissions detected early. An <code>IllegalArgumentException</code> will be
-     * thrown by the <code>AbstractSecurityInterceptor</code> if you set this property to <code>true</code> and an
-     * attempt is made to invoke a secure object that has no configuration attributes.
-     *
-     * @param rejectPublicInvocations set to <code>true</code> to reject invocations of secure objects that have no
-     *        configuration attributes (by default it is <code>false</code> which treats undeclared secure objects as
-     *        "public" or unauthorized)
-     */
-    public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
-        this.rejectPublicInvocations = rejectPublicInvocations;
-    }
-
-    public void setRunAsManager(RunAsManager runAsManager) {
-        this.runAsManager = runAsManager;
-    }
-
-    public void setValidateConfigAttributes(boolean validateConfigAttributes) {
-        this.validateConfigAttributes = validateConfigAttributes;
-    }
-
-    private void publishEvent(ApplicationEvent event) {
-        if (this.eventPublisher != null) {
-            this.eventPublisher.publishEvent(event);
-        }
-    }
+		if (afterInvocationManager != null) {
+			// Attempt after invocation handling
+			try {
+				returnedObject = afterInvocationManager.decide(token.getAuthentication(), token.getSecureObject(),
+						token.getAttr(), returnedObject);
+			}
+			catch (AccessDeniedException accessDeniedException) {
+				AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token
+						.getAttr(), token.getAuthentication(), accessDeniedException);
+				publishEvent(event);
+
+				throw accessDeniedException;
+			}
+		}
+
+		return returnedObject;
+	}
+
+	public void afterPropertiesSet() throws Exception {
+		Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()");
+
+		Assert.notNull(this.messages, "A message source must be set");
+
+		if (!isSetAuthenticationManagerInvoked) {
+			autoDetectAuthenticationManager();
+		}
+		Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
+
+		if (!isSetAccessDecisionManagerInvoked) {
+			autoDetectAccessDecisionManager();
+		}
+		Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
+
+		Assert.notNull(this.runAsManager, "A RunAsManager is required");
+
+		Assert.notNull(this.obtainObjectDefinitionSource(), "An ObjectDefinitionSource is required");
+
+		Assert.isTrue(this.obtainObjectDefinitionSource().supports(getSecureObjectClass()),
+				"ObjectDefinitionSource does not support secure object class: " + getSecureObjectClass());
+
+		Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()),
+				"RunAsManager does not support secure object class: " + getSecureObjectClass());
+
+		Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()),
+				"AccessDecisionManager does not support secure object class: " + getSecureObjectClass());
+
+		if (this.afterInvocationManager != null) {
+			Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()),
+					"AfterInvocationManager does not support secure object class: " + getSecureObjectClass());
+		}
+
+		if (this.validateConfigAttributes) {
+			Iterator iter = this.obtainObjectDefinitionSource().getConfigAttributeDefinitions();
+
+			if (iter == null) {
+				logger.warn("Could not validate configuration attributes as the MethodDefinitionSource did not return "
+						+ "a ConfigAttributeDefinition Iterator");
+				return;
+			}
+
+			Set unsupportedAttrs = new HashSet();
+
+			while (iter.hasNext()) {
+				ConfigAttributeDefinition def = (ConfigAttributeDefinition) iter.next();
+				Iterator attributes = def.getConfigAttributes();
+
+				while (attributes.hasNext()) {
+					ConfigAttribute attr = (ConfigAttribute) attributes.next();
+
+					if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr)
+							&& ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) {
+						unsupportedAttrs.add(attr);
+					}
+				}
+			}
+
+			if (unsupportedAttrs.size() != 0) {
+				throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs);
+			}
+
+			logger.info("Validated configuration attributes");
+		}
+	}
+
+	/**
+	 * Introspects the <code>Applicationcontext</code> for the single instance
+	 * of <code>AccessDecisionManager</code>. If more than one instance of
+	 * <code>AccessDecisionManager</code> is found, the method uses the first
+	 * one detected.
+	 * 
+	 * @param applicationContext to locate the instance
+	 */
+	private void autoDetectAccessDecisionManager() {
+		if (applicationContext != null) {
+			Map map = applicationContext.getBeansOfType(AccessDecisionManager.class);
+			if (map.size() > 0)
+				setAccessDecisionManager((AccessDecisionManager) map.values().iterator().next());
+		}
+	}
+
+	/**
+	 * Introspects the <code>Applicationcontext</code> for the single instance
+	 * of <code>AuthenticationManager</code>. If found invoke
+	 * setAuthenticationManager method by providing the found instance of
+	 * authenticationManager as a method parameter. If more than one instance of
+	 * <code>AuthenticationManager</code> is found, the method throws
+	 * <code>IllegalStateException</code>.
+	 * 
+	 * @param applicationContext to locate the instance
+	 */
+	private void autoDetectAuthenticationManager() {
+		if (applicationContext != null) {
+			Map map = applicationContext.getBeansOfType(AuthenticationManager.class);
+			if (map.size() > 1) {
+				throw new IllegalArgumentException(
+						"More than one AuthenticationManager beans detected please refer to the one using "
+								+ " [ authenticationManager  ] " + "property");
+			}
+			else if (map.size() == 1) {
+				setAuthenticationManager((AuthenticationManager) map.values().iterator().next());
+			}
+		}
+
+	}
+
+	protected InterceptorStatusToken beforeInvocation(Object object) {
+		Assert.notNull(object, "Object was null");
+
+		if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
+			throw new IllegalArgumentException("Security invocation attempted for object "
+					+ object.getClass().getName()
+					+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+					+ getSecureObjectClass());
+		}
+
+		ConfigAttributeDefinition attr = this.obtainObjectDefinitionSource().getAttributes(object);
+
+		if (attr == null) {
+			if (rejectPublicInvocations) {
+				throw new IllegalArgumentException(
+						"No public invocations are allowed via this AbstractSecurityInterceptor. "
+								+ "This indicates a configuration error because the "
+								+ "AbstractSecurityInterceptor.rejectPublicInvocations property is set to 'true'");
+			}
+
+			if (logger.isDebugEnabled()) {
+				logger.debug("Public object - authentication not attempted");
+			}
+
+			publishEvent(new PublicInvocationEvent(object));
+
+			return null; // no further work post-invocation
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Secure object: " + object.toString() + "; ConfigAttributes: " + attr.toString());
+		}
+
+		if (SecurityContextHolder.getContext().getAuthentication() == null) {
+			credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
+					"An Authentication object was not found in the SecurityContext"), object, attr);
+		}
+
+		// Attempt authentication if not already authenticated, or user always
+		// wants reauthentication
+		Authentication authenticated;
+
+		if (!SecurityContextHolder.getContext().getAuthentication().isAuthenticated() || alwaysReauthenticate) {
+			try {
+				authenticated = this.authenticationManager.authenticate(SecurityContextHolder.getContext()
+						.getAuthentication());
+			}
+			catch (AuthenticationException authenticationException) {
+				throw authenticationException;
+			}
+
+			// We don't authenticated.setAuthentication(true), because each
+			// provider should do that
+			if (logger.isDebugEnabled()) {
+				logger.debug("Successfully Authenticated: " + authenticated.toString());
+			}
+
+			SecurityContextHolder.getContext().setAuthentication(authenticated);
+		}
+		else {
+			authenticated = SecurityContextHolder.getContext().getAuthentication();
+
+			if (logger.isDebugEnabled()) {
+				logger.debug("Previously Authenticated: " + authenticated.toString());
+			}
+		}
+
+		// Attempt authorization
+		try {
+			this.accessDecisionManager.decide(authenticated, object, attr);
+		}
+		catch (AccessDeniedException accessDeniedException) {
+			AuthorizationFailureEvent event = new AuthorizationFailureEvent(object, attr, authenticated,
+					accessDeniedException);
+			publishEvent(event);
+
+			throw accessDeniedException;
+		}
+
+		if (logger.isDebugEnabled()) {
+			logger.debug("Authorization successful");
+		}
+
+		AuthorizedEvent event = new AuthorizedEvent(object, attr, authenticated);
+		publishEvent(event);
+
+		// Attempt to run as a different user
+		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attr);
+
+		if (runAs == null) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("RunAsManager did not change Authentication object");
+			}
+
+			// no further work post-invocation
+			return new InterceptorStatusToken(authenticated, false, attr, object);
+		}
+		else {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Switching to RunAs Authentication: " + runAs.toString());
+			}
+
+			SecurityContextHolder.getContext().setAuthentication(runAs);
+
+			// revert to token.Authenticated post-invocation
+			return new InterceptorStatusToken(authenticated, true, attr, object);
+		}
+	}
+
+	/**
+	 * Helper method which generates an exception containing the passed reason,
+	 * and publishes an event to the application context.
+	 * <p>
+	 * Always throws an exception.
+	 * </p>
+	 * 
+	 * @param reason to be provided in the exception detail
+	 * @param secureObject that was being called
+	 * @param configAttribs that were defined for the secureObject
+	 */
+	private void credentialsNotFound(String reason, Object secureObject, ConfigAttributeDefinition configAttribs) {
+		AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason);
+
+		AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject,
+				configAttribs, exception);
+		publishEvent(event);
+
+		throw exception;
+	}
+
+	public AccessDecisionManager getAccessDecisionManager() {
+		return accessDecisionManager;
+	}
+
+	public AfterInvocationManager getAfterInvocationManager() {
+		return afterInvocationManager;
+	}
+
+	public AuthenticationManager getAuthenticationManager() {
+		return this.authenticationManager;
+	}
+
+	public RunAsManager getRunAsManager() {
+		return runAsManager;
+	}
+
+	/**
+	 * Indicates the type of secure objects the subclass will be presenting to
+	 * the abstract parent for processing. This is used to ensure collaborators
+	 * wired to the <code>AbstractSecurityInterceptor</code> all support the
+	 * indicated secure object class.
+	 * 
+	 * @return the type of secure object the subclass provides services for
+	 */
+	public abstract Class getSecureObjectClass();
+
+	public boolean isAlwaysReauthenticate() {
+		return alwaysReauthenticate;
+	}
+
+	public boolean isRejectPublicInvocations() {
+		return rejectPublicInvocations;
+	}
+
+	public boolean isValidateConfigAttributes() {
+		return validateConfigAttributes;
+	}
+
+	public abstract ObjectDefinitionSource obtainObjectDefinitionSource();
+
+	public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
+		isSetAccessDecisionManagerInvoked = true;
+		this.accessDecisionManager = accessDecisionManager;
+	}
+
+	public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) {
+		this.afterInvocationManager = afterInvocationManager;
+	}
+
+	/**
+	 * Indicates whether the <code>AbstractSecurityInterceptor</code> should
+	 * ignore the {@link Authentication#isAuthenticated()} property. Defaults to
+	 * <code>false</code>, meaning by default the
+	 * <code>Authentication.isAuthenticated()</code> property is trusted and
+	 * re-authentication will not occur if the principal has already been
+	 * authenticated.
+	 * 
+	 * @param alwaysReauthenticate <code>true</code> to force
+	 * <code>AbstractSecurityInterceptor</code> to disregard the value of
+	 * <code>Authentication.isAuthenticated()</code> and always
+	 * re-authenticate the request (defaults to <code>false</code>).
+	 */
+	public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
+		this.alwaysReauthenticate = alwaysReauthenticate;
+	}
+
+	public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
+		this.eventPublisher = applicationEventPublisher;
+	}
+
+	public void setAuthenticationManager(AuthenticationManager newManager) {
+		isSetAuthenticationManagerInvoked = true;
+		this.authenticationManager = newManager;
+	}
+
+	public void setMessageSource(MessageSource messageSource) {
+		this.messages = new MessageSourceAccessor(messageSource);
+	}
+
+	/**
+	 * By rejecting public invocations (and setting this property to
+	 * <code>true</code>), essentially you are ensuring that every secure
+	 * object invocation advised by <code>AbstractSecurityInterceptor</code>
+	 * has a configuration attribute defined. This is useful to ensure a "fail
+	 * safe" mode where undeclared secure objects will be rejected and
+	 * configuration omissions detected early. An
+	 * <code>IllegalArgumentException</code> will be thrown by the
+	 * <code>AbstractSecurityInterceptor</code> if you set this property to
+	 * <code>true</code> and an attempt is made to invoke a secure object that
+	 * has no configuration attributes.
+	 * 
+	 * @param rejectPublicInvocations set to <code>true</code> to reject
+	 * invocations of secure objects that have no configuration attributes (by
+	 * default it is <code>false</code> which treats undeclared secure objects
+	 * as "public" or unauthorized)
+	 */
+	public void setRejectPublicInvocations(boolean rejectPublicInvocations) {
+		this.rejectPublicInvocations = rejectPublicInvocations;
+	}
+
+	public void setRunAsManager(RunAsManager runAsManager) {
+		this.runAsManager = runAsManager;
+	}
+
+	public void setValidateConfigAttributes(boolean validateConfigAttributes) {
+		this.validateConfigAttributes = validateConfigAttributes;
+	}
+
+	private void publishEvent(ApplicationEvent event) {
+		if (this.eventPublisher != null) {
+			this.eventPublisher.publishEvent(event);
+		}
+	}
+
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		this.applicationContext = applicationContext;
+	}
 }

+ 142 - 109
core/src/main/java/org/acegisecurity/vote/AbstractAccessDecisionManager.java

@@ -15,126 +15,159 @@
 
 package org.acegisecurity.vote;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
 import org.acegisecurity.AccessDecisionManager;
 import org.acegisecurity.AccessDeniedException;
 import org.acegisecurity.AcegiMessageSource;
 import org.acegisecurity.ConfigAttribute;
-
+import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
-
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.MessageSource;
 import org.springframework.context.MessageSourceAware;
 import org.springframework.context.support.MessageSourceAccessor;
-
+import org.springframework.core.OrderComparator;
+import org.springframework.core.Ordered;
 import org.springframework.util.Assert;
 
-import java.util.Iterator;
-import java.util.List;
-
-
 /**
- * Abstract implementation of {@link AccessDecisionManager}.<p>Handles configuration of a bean context defined list
- * of  {@link AccessDecisionVoter}s and the access control behaviour if all  voters abstain from voting (defaults to
- * deny access).</p>
+ * Abstract implementation of {@link AccessDecisionManager}.
+ * <p>
+ * Handles configuration of a bean context defined list of
+ * {@link AccessDecisionVoter}s and the access control behaviour if all voters
+ * abstain from voting (defaults to deny access).
+ * </p>
  */
 public abstract class AbstractAccessDecisionManager implements AccessDecisionManager, InitializingBean,
-    MessageSourceAware {
-    //~ Instance fields ================================================================================================
-
-    private List decisionVoters;
-    protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
-    private boolean allowIfAllAbstainDecisions = false;
-
-    //~ Methods ========================================================================================================
-
-    public void afterPropertiesSet() throws Exception {
-        checkIfValidList(this.decisionVoters);
-        Assert.notNull(this.messages, "A message source must be set");
-    }
-
-    protected final void checkAllowIfAllAbstainDecisions() {
-        if (!this.isAllowIfAllAbstainDecisions()) {
-            throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
-                    "Access is denied"));
-        }
-    }
-
-    private void checkIfValidList(List listToCheck) {
-        if ((listToCheck == null) || (listToCheck.size() == 0)) {
-            throw new IllegalArgumentException("A list of AccessDecisionVoters is required");
-        }
-    }
-
-    public List getDecisionVoters() {
-        return this.decisionVoters;
-    }
-
-    public boolean isAllowIfAllAbstainDecisions() {
-        return allowIfAllAbstainDecisions;
-    }
-
-    public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
-        this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
-    }
-
-    public void setDecisionVoters(List newList) {
-        checkIfValidList(newList);
-
-        Iterator iter = newList.iterator();
-
-        while (iter.hasNext()) {
-            Object currentObject = null;
-
-            try {
-                currentObject = iter.next();
-
-                AccessDecisionVoter attemptToCast = (AccessDecisionVoter) currentObject;
-            } catch (ClassCastException cce) {
-                throw new IllegalArgumentException("AccessDecisionVoter " + currentObject.getClass().getName()
-                    + " must implement AccessDecisionVoter");
-            }
-        }
-
-        this.decisionVoters = newList;
-    }
-
-    public void setMessageSource(MessageSource messageSource) {
-        this.messages = new MessageSourceAccessor(messageSource);
-    }
-
-    public boolean supports(ConfigAttribute attribute) {
-        Iterator iter = this.decisionVoters.iterator();
-
-        while (iter.hasNext()) {
-            AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
-
-            if (voter.supports(attribute)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Iterates through all <code>AccessDecisionVoter</code>s and ensures each can support the presented class.<p>If
-     * one or more voters cannot support the presented class, <code>false</code> is returned.</p>
-     *
-     * @param clazz DOCUMENT ME!
-     *
-     * @return DOCUMENT ME!
-     */
-    public boolean supports(Class clazz) {
-        Iterator iter = this.decisionVoters.iterator();
-
-        while (iter.hasNext()) {
-            AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
-
-            if (!voter.supports(clazz)) {
-                return false;
-            }
-        }
-
-        return true;
-    }
+		MessageSourceAware, ApplicationContextAware {
+	// ~ Instance fields
+	// ================================================================================================
+
+	private List decisionVoters;
+
+	protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
+
+	private boolean allowIfAllAbstainDecisions = false;
+
+	private boolean isSetDecisionVotersInvoked = false;
+
+	private ApplicationContext applicationContext;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public void afterPropertiesSet() throws Exception {
+		if (!isSetDecisionVotersInvoked) {
+			autoDetectVoters();
+		}
+		Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required");
+		Assert.notNull(this.messages, "A message source must be set");
+	}
+
+	private void autoDetectVoters() {
+		Map map = this.applicationContext.getBeansOfType(AccessDecisionVoter.class);
+		List list = new ArrayList();
+		for(Iterator it = map.values().iterator(); it.hasNext();) {
+			list.add((it.next()));
+		}
+		Collections.sort(list, new OrderComparator());
+		setDecisionVoters(list);
+	}
+
+	protected final void checkAllowIfAllAbstainDecisions() {
+		if (!this.isAllowIfAllAbstainDecisions()) {
+			throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
+					"Access is denied"));
+		}
+	}
+
+	public List getDecisionVoters() {
+		return this.decisionVoters;
+	}
+
+	public boolean isAllowIfAllAbstainDecisions() {
+		return allowIfAllAbstainDecisions;
+	}
+
+	public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) {
+		this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions;
+	}
+
+	public void setDecisionVoters(List newList) {
+		isSetDecisionVotersInvoked = true;
+		Assert.notEmpty(newList);
+
+		Iterator iter = newList.iterator();
+
+		while (iter.hasNext()) {
+			Object currentObject = null;
+
+			try {
+				currentObject = iter.next();
+
+				AccessDecisionVoter attemptToCast = (AccessDecisionVoter) currentObject;
+			}
+			catch (ClassCastException cce) {
+				throw new IllegalArgumentException("AccessDecisionVoter " + currentObject.getClass().getName()
+						+ " must implement AccessDecisionVoter");
+			}
+		}
+
+		this.decisionVoters = newList;
+	}
+
+	public void setMessageSource(MessageSource messageSource) {
+		this.messages = new MessageSourceAccessor(messageSource);
+	}
+
+	public boolean supports(ConfigAttribute attribute) {
+		Iterator iter = this.decisionVoters.iterator();
+
+		while (iter.hasNext()) {
+			AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
+
+			if (voter.supports(attribute)) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Iterates through all <code>AccessDecisionVoter</code>s and ensures
+	 * each can support the presented class.
+	 * <p>
+	 * If one or more voters cannot support the presented class,
+	 * <code>false</code> is returned.
+	 * </p>
+	 * 
+	 * @param clazz DOCUMENT ME!
+	 * 
+	 * @return DOCUMENT ME!
+	 */
+	public boolean supports(Class clazz) {
+		Iterator iter = this.decisionVoters.iterator();
+
+		while (iter.hasNext()) {
+			AccessDecisionVoter voter = (AccessDecisionVoter) iter.next();
+
+			if (!voter.supports(clazz)) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		this.applicationContext = applicationContext;
+	}
+
 }

+ 15 - 2
core/src/main/java/org/acegisecurity/vote/AuthenticatedVoter.java

@@ -21,6 +21,7 @@ import org.acegisecurity.AuthenticationTrustResolverImpl;
 import org.acegisecurity.ConfigAttribute;
 import org.acegisecurity.ConfigAttributeDefinition;
 
+import org.springframework.core.Ordered;
 import org.springframework.util.Assert;
 
 import java.util.Iterator;
@@ -41,16 +42,19 @@ import java.util.Iterator;
  * @author Ben Alex
  * @version $Id$
  */
-public class AuthenticatedVoter implements AccessDecisionVoter {
+public class AuthenticatedVoter implements AccessDecisionVoter, Ordered {
     //~ Static fields/initializers =====================================================================================
 
     public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY";
     public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED";
     public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY";
-
+    public static int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
     //~ Instance fields ================================================================================================
 
     private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
+    
+    private int order = DEFAULT_ORDER;
+    
 
     //~ Methods ========================================================================================================
 
@@ -120,4 +124,13 @@ public class AuthenticatedVoter implements AccessDecisionVoter {
 
         return result;
     }
+
+	public void setOrder(int order) {
+		this.order = order;
+	}
+
+	public int getOrder() {
+		return order;
+	}
+
 }

+ 109 - 77
core/src/main/java/org/acegisecurity/vote/RoleVoter.java

@@ -18,89 +18,121 @@ package org.acegisecurity.vote;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.ConfigAttribute;
 import org.acegisecurity.ConfigAttributeDefinition;
+import org.springframework.core.Ordered;
 
 import java.util.Iterator;
 
-
 /**
- * <p>Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating that it is a role. The
- * default prefix string is <Code>ROLE_</code>, but this may be overriden to any value. It may also be set to empty,
- * which means that essentially any attribute will be voted on. As described further below, the effect of an empty
- * prefix may not be quite desireable.</p>
- *  <p>Abstains from voting if no configuration attribute commences with the  role prefix. Votes to grant access if
- * there is an exact matching {@link org.acegisecurity.GrantedAuthority} to a <code>ConfigAttribute</code> starting
- * with the role prefix. Votes to deny access if there is no exact matching <code>GrantedAuthority</code>  to a
- * <code>ConfigAttribute</code> starting with the role prefix.</p>
- *  <p>An empty role prefix means that the voter will vote for every ConfigAttribute. When there are different
- * categories of ConfigAttributes used, this will not be optimal since the voter will be voting for attributes which
- * do not represent roles. However, this option may be of some use when using preexisting role names without a prefix,
- * and no ability exists to prefix them with a role prefix on reading them in, such as provided for example in  {@link
- * org.acegisecurity.userdetails.jdbc.JdbcDaoImpl}.</p>
- *  <p>All comparisons and prefixes are case sensitive.</p>
- *
+ * <p>
+ * Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix
+ * indicating that it is a role. The default prefix string is <Code>ROLE_</code>,
+ * but this may be overriden to any value. It may also be set to empty, which
+ * means that essentially any attribute will be voted on. As described further
+ * below, the effect of an empty prefix may not be quite desireable.
+ * </p>
+ * <p>
+ * Abstains from voting if no configuration attribute commences with the role
+ * prefix. Votes to grant access if there is an exact matching
+ * {@link org.acegisecurity.GrantedAuthority} to a <code>ConfigAttribute</code>
+ * starting with the role prefix. Votes to deny access if there is no exact
+ * matching <code>GrantedAuthority</code> to a <code>ConfigAttribute</code>
+ * starting with the role prefix.
+ * </p>
+ * <p>
+ * An empty role prefix means that the voter will vote for every
+ * ConfigAttribute. When there are different categories of ConfigAttributes
+ * used, this will not be optimal since the voter will be voting for attributes
+ * which do not represent roles. However, this option may be of some use when
+ * using preexisting role names without a prefix, and no ability exists to
+ * prefix them with a role prefix on reading them in, such as provided for
+ * example in {@link org.acegisecurity.userdetails.jdbc.JdbcDaoImpl}.
+ * </p>
+ * <p>
+ * All comparisons and prefixes are case sensitive.
+ * </p>
+ * 
  * @author Ben Alex
  * @author colin sampaleanu
  * @version $Id$
  */
-public class RoleVoter implements AccessDecisionVoter {
-    //~ Instance fields ================================================================================================
-
-    private String rolePrefix = "ROLE_";
-
-    //~ Methods ========================================================================================================
-
-    public String getRolePrefix() {
-        return rolePrefix;
-    }
-
-    /**
-     * Allows the default role prefix of <code>ROLE_</code> to be overriden. May be set to an empty value,
-     * although this is usually not desireable.
-     *
-     * @param rolePrefix the new prefix
-     */
-    public void setRolePrefix(String rolePrefix) {
-        this.rolePrefix = rolePrefix;
-    }
-
-    public boolean supports(ConfigAttribute attribute) {
-        if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * This implementation supports any type of class, because it does not query the presented secure object.
-     *
-     * @param clazz the secure object
-     *
-     * @return always <code>true</code>
-     */
-    public boolean supports(Class clazz) {
-        return true;
-    }
-
-    public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
-        int result = ACCESS_ABSTAIN;
-        Iterator iter = config.getConfigAttributes();
-
-        while (iter.hasNext()) {
-            ConfigAttribute attribute = (ConfigAttribute) iter.next();
-
-            if (this.supports(attribute)) {
-                result = ACCESS_DENIED;
-
-                // Attempt to find a matching granted authority
-                for (int i = 0; i < authentication.getAuthorities().length; i++) {
-                    if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) {
-                        return ACCESS_GRANTED;
-                    }
-                }
-            }
-        }
-
-        return result;
-    }
+public class RoleVoter implements AccessDecisionVoter, Ordered {
+	// ~ Static fields/initializers
+	// =====================================================================================
+	public static int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
+
+	// ~ Instance fields
+	// ================================================================================================
+
+	private String rolePrefix = "ROLE_";
+
+	private int order = DEFAULT_ORDER;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public String getRolePrefix() {
+		return rolePrefix;
+	}
+
+	/**
+	 * Allows the default role prefix of <code>ROLE_</code> to be overriden.
+	 * May be set to an empty value, although this is usually not desireable.
+	 * 
+	 * @param rolePrefix the new prefix
+	 */
+	public void setRolePrefix(String rolePrefix) {
+		this.rolePrefix = rolePrefix;
+	}
+
+	public boolean supports(ConfigAttribute attribute) {
+		if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+
+	/**
+	 * This implementation supports any type of class, because it does not query
+	 * the presented secure object.
+	 * 
+	 * @param clazz the secure object
+	 * 
+	 * @return always <code>true</code>
+	 */
+	public boolean supports(Class clazz) {
+		return true;
+	}
+
+	public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
+		int result = ACCESS_ABSTAIN;
+		Iterator iter = config.getConfigAttributes();
+
+		while (iter.hasNext()) {
+			ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+			if (this.supports(attribute)) {
+				result = ACCESS_DENIED;
+
+				// Attempt to find a matching granted authority
+				for (int i = 0; i < authentication.getAuthorities().length; i++) {
+					if (attribute.getAttribute().equals(authentication.getAuthorities()[i].getAuthority())) {
+						return ACCESS_GRANTED;
+					}
+				}
+			}
+		}
+
+		return result;
+	}
+
+	public void setOrder(int order) {
+		this.order = order;
+	}
+
+	public int getOrder() {
+		return order;
+	}
+
 }

+ 53 - 34
core/src/test/java/org/acegisecurity/vote/DenyAgainVoter.java

@@ -18,45 +18,64 @@ package org.acegisecurity.vote;
 import org.acegisecurity.Authentication;
 import org.acegisecurity.ConfigAttribute;
 import org.acegisecurity.ConfigAttributeDefinition;
+import org.springframework.core.Ordered;
 
 import java.util.Iterator;
 
-
 /**
- * Implementation of an {@link AccessDecisionVoter} for unit testing.<p>If the {@link
- * ConfigAttribute#getAttribute()} has a value of <code>DENY_AGAIN_FOR_SURE</code>, the voter will vote to deny
- * access.</p>
- *  <p>All comparisons are case sensitive.</p>
- *
+ * Implementation of an {@link AccessDecisionVoter} for unit testing.
+ * <p>
+ * If the {@link ConfigAttribute#getAttribute()} has a value of
+ * <code>DENY_AGAIN_FOR_SURE</code>, the voter will vote to deny access.
+ * </p>
+ * <p>
+ * All comparisons are case sensitive.
+ * </p>
+ * 
  * @author Ben Alex
  * @version $Id$
  */
-public class DenyAgainVoter implements AccessDecisionVoter {
-    //~ Methods ========================================================================================================
-
-    public boolean supports(ConfigAttribute attribute) {
-        if ("DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute())) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    public boolean supports(Class clazz) {
-        return true;
-    }
-
-    public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
-        Iterator iter = config.getConfigAttributes();
-
-        while (iter.hasNext()) {
-            ConfigAttribute attribute = (ConfigAttribute) iter.next();
-
-            if (this.supports(attribute)) {
-                return ACCESS_DENIED;
-            }
-        }
-
-        return ACCESS_ABSTAIN;
-    }
+public class DenyAgainVoter implements AccessDecisionVoter, Ordered {
+	public static int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE;
+
+	private int order = DEFAULT_ORDER;
+
+	// ~ Methods
+	// ========================================================================================================
+
+	public boolean supports(ConfigAttribute attribute) {
+		if ("DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute())) {
+			return true;
+		}
+		else {
+			return false;
+		}
+	}
+
+	public boolean supports(Class clazz) {
+		return true;
+	}
+
+	public int vote(Authentication authentication, Object object, ConfigAttributeDefinition config) {
+		Iterator iter = config.getConfigAttributes();
+
+		while (iter.hasNext()) {
+			ConfigAttribute attribute = (ConfigAttribute) iter.next();
+
+			if (this.supports(attribute)) {
+				return ACCESS_DENIED;
+			}
+		}
+
+		return ACCESS_ABSTAIN;
+	}
+	
+	public void setOrder(int order) {
+		this.order = order;
+	}
+
+	public int getOrder() {
+		return order;
+	}
+
 }