diff --git a/core/src/main/java/org/springframework/security/config/ConfigUtils.java b/core/src/main/java/org/springframework/security/config/ConfigUtils.java
index 3eb82b393c..0321098027 100644
--- a/core/src/main/java/org/springframework/security/config/ConfigUtils.java
+++ b/core/src/main/java/org/springframework/security/config/ConfigUtils.java
@@ -1,5 +1,9 @@
package org.springframework.security.config;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@@ -13,14 +17,10 @@ import org.springframework.security.userdetails.UserDetailsService;
import org.springframework.security.vote.AffirmativeBased;
import org.springframework.security.vote.AuthenticatedVoter;
import org.springframework.security.vote.RoleVoter;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import java.util.Map;
+import org.springframework.util.StringUtils;
/**
- * Utitily methods used internally by the Spring Security namespace configuration code.
+ * Utility methods used internally by the Spring Security namespace configuration code.
*
* @author Luke Taylor
* @author Ben Alex
@@ -44,6 +44,18 @@ public abstract class ConfigUtils {
parserContext.getRegistry().registerBeanDefinition(BeanIds.ACCESS_MANAGER, accessMgr);
}
}
+
+ public static int countNonEmpty(String[] objects) {
+ int nonNulls = 0;
+
+ for (int i = 0; i < objects.length; i++) {
+ if (StringUtils.hasText(objects[i])) {
+ nonNulls++;
+ }
+ }
+
+ return nonNulls;
+ }
public static void addVoter(BeanDefinition voter, ParserContext parserContext) {
registerDefaultAccessManagerIfNecessary(parserContext);
diff --git a/core/src/main/java/org/springframework/security/config/OrderedFilterBeanDefinitionDecorator.java b/core/src/main/java/org/springframework/security/config/OrderedFilterBeanDefinitionDecorator.java
index bf84421984..d516162300 100644
--- a/core/src/main/java/org/springframework/security/config/OrderedFilterBeanDefinitionDecorator.java
+++ b/core/src/main/java/org/springframework/security/config/OrderedFilterBeanDefinitionDecorator.java
@@ -1,25 +1,25 @@
package org.springframework.security.config;
-import org.springframework.security.ui.FilterChainOrder;
-import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
-import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.beans.factory.config.BeanDefinitionHolder;
-import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.core.Ordered;
-import org.springframework.util.StringUtils;
-import org.springframework.util.Assert;
-
-import org.w3c.dom.Node;
-import org.w3c.dom.Element;
+import java.io.IOException;
import javax.servlet.Filter;
+import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
-import javax.servlet.FilterChain;
-import java.io.IOException;
+
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanDefinitionHolder;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.xml.BeanDefinitionDecorator;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.core.Ordered;
+import org.springframework.security.ui.FilterChainOrder;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
/**
* Replaces a Spring bean of type "Filter" with a wrapper class which implements the Ordered
@@ -33,6 +33,7 @@ public class OrderedFilterBeanDefinitionDecorator implements BeanDefinitionDecor
public static final String ATT_AFTER = "after";
public static final String ATT_BEFORE = "before";
+ public static final String ATT_POSITION = "position";
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder holder, ParserContext parserContext) {
Element elt = (Element)node;
@@ -56,7 +57,17 @@ public class OrderedFilterBeanDefinitionDecorator implements BeanDefinitionDecor
private String getOrder(Element elt, ParserContext pc) {
String after = elt.getAttribute(ATT_AFTER);
String before = elt.getAttribute(ATT_BEFORE);
-
+ String position = elt.getAttribute(ATT_POSITION);
+
+ if(ConfigUtils.countNonEmpty(new String[] {after, before, position}) != 1) {
+ pc.getReaderContext().error("A single '" + ATT_AFTER + "', '" + ATT_BEFORE + "', or '" +
+ ATT_POSITION + "' attribute must be supplied", pc.extractSource(elt));
+ }
+
+ if (StringUtils.hasText(position)) {
+ return Integer.toString(FilterChainOrder.getOrder(position));
+ }
+
if (StringUtils.hasText(after)) {
return Integer.toString(FilterChainOrder.getOrder(after) + 1);
}
diff --git a/core/src/main/java/org/springframework/security/ui/FilterChainOrder.java b/core/src/main/java/org/springframework/security/ui/FilterChainOrder.java
index 5d45030a25..cfd82eda9b 100644
--- a/core/src/main/java/org/springframework/security/ui/FilterChainOrder.java
+++ b/core/src/main/java/org/springframework/security/ui/FilterChainOrder.java
@@ -43,7 +43,7 @@ public abstract class FilterChainOrder {
private static final Map filterNameToOrder = new LinkedHashMap();
static {
- filterNameToOrder.put("FIRST", new Integer(FILTER_CHAIN_FIRST));
+ filterNameToOrder.put("FIRST", Integer.MIN_VALUE);
filterNameToOrder.put("CHANNEL_FILTER", new Integer(CHANNEL_FILTER));
filterNameToOrder.put("CONCURRENT_SESSION_FILTER", new Integer(CONCURRENT_SESSION_FILTER));
filterNameToOrder.put("SESSION_CONTEXT_INTEGRATION_FILTER", new Integer(HTTP_SESSION_CONTEXT_FILTER));
@@ -61,6 +61,7 @@ public abstract class FilterChainOrder {
filterNameToOrder.put("NTLM_FILTER", new Integer(NTLM_FILTER));
filterNameToOrder.put("FILTER_SECURITY_INTERCEPTOR", new Integer(FILTER_SECURITY_INTERCEPTOR));
filterNameToOrder.put("SWITCH_USER_FILTER", new Integer(SWITCH_USER_FILTER));
+ filterNameToOrder.put("LAST", Integer.MAX_VALUE);
}
/** Allows filters to be used by name in the XSD file without explicit reference to Java constants */
diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc
index 4ab27a4344..8d60ff0d91 100644
--- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc
+++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc
@@ -420,14 +420,19 @@ any-user-service = user-service | jdbc-user-service | ldap-user-service
custom-filter =
## Used to indicate that a filter bean declaration should be incorporated into the security filter chain. If neither the 'after' or 'before' options are supplied, then the filter must implement the Ordered interface directly.
- element custom-filter {after | before}?
+ element custom-filter {after | before | position}?
after =
## The filter immediately after which the custom-filter should be placed in the chain. This feature will only be needed by advanced users who wish to mix their own filters into the security filter chain and have some knowledge of the standard Spring Security filters. The filter names map to specific Spring Security implementation filters.
attribute after {named-security-filter}
before =
## The filter immediately before which the custom-filter should be placed in the chain
attribute before {named-security-filter}
+position =
+ ## The explicit position at which the custom-filter should be placed in the chain. Use if you are replacing a standard filter.
+ attribute position {named-security-filter}
-named-security-filter = "FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SESSION_CONTEXT_INTEGRATION_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_PROCESSING_FILTER" | "AUTHENTICATION_PROCESSING_FILTER" | "BASIC_PROCESSING_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "NTLM_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER"
+
+
+named-security-filter = "FIRST" | "CHANNEL_FILTER" | "CONCURRENT_SESSION_FILTER" | "SESSION_CONTEXT_INTEGRATION_FILTER" | "LOGOUT_FILTER" | "X509_FILTER" | "PRE_AUTH_FILTER" | "CAS_PROCESSING_FILTER" | "AUTHENTICATION_PROCESSING_FILTER" | "BASIC_PROCESSING_FILTER" | "SERVLET_API_SUPPORT_FILTER" | "REMEMBER_ME_FILTER" | "ANONYMOUS_FILTER" | "EXCEPTION_TRANSLATION_FILTER" | "NTLM_FILTER" | "FILTER_SECURITY_INTERCEPTOR" | "SWITCH_USER_FILTER" | "LAST"
diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd
index 2576f2dfec..845b03293f 100644
--- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd
+++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd
@@ -1173,6 +1173,12 @@
in the chain
+
+
+ The explicit position at which the custom-filter should be placed in the
+ chain. Use if you are replacing a standard filter.
+
+
@@ -1194,6 +1200,14 @@
+
+
+
+ The explicit position at which the custom-filter should be placed in the
+ chain. Use if you are replacing a standard filter.
+
+
+
@@ -1213,6 +1227,7 @@
+
diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
index 9b50b6ca7a..22d0260510 100644
--- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
+++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java
@@ -248,12 +248,17 @@ public class HttpSecurityBeanDefinitionParserTests {
"" +
" " +
"" +
- "");
+ "" +
+ " " +
+ "" +
+ "");
List filters = getFilters("/someurl");
- assertEquals(12, filters.size());
- assertTrue(filters.get(1) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
- assertEquals("userFilter", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(1)).getBeanName());
+ assertEquals(13, filters.size());
+ assertTrue(filters.get(0) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
+ assertTrue(filters.get(2) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
+ assertEquals("userFilter", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(2)).getBeanName());
+ assertEquals("userFilter2", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(0)).getBeanName());
}
@Test