SEC-764: Added support for "position" attribute. Also added "LAST" as an option for filter position.

This commit is contained in:
Luke Taylor 2008-04-11 17:01:08 +00:00
parent d9fc36da41
commit 3b3d339393
6 changed files with 77 additions and 28 deletions

View File

@ -1,5 +1,9 @@
package org.springframework.security.config; 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.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference; 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.AffirmativeBased;
import org.springframework.security.vote.AuthenticatedVoter; import org.springframework.security.vote.AuthenticatedVoter;
import org.springframework.security.vote.RoleVoter; import org.springframework.security.vote.RoleVoter;
import org.springframework.util.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Map;
/** /**
* 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 Luke Taylor
* @author Ben Alex * @author Ben Alex
@ -44,6 +44,18 @@ public abstract class ConfigUtils {
parserContext.getRegistry().registerBeanDefinition(BeanIds.ACCESS_MANAGER, accessMgr); 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) { public static void addVoter(BeanDefinition voter, ParserContext parserContext) {
registerDefaultAccessManagerIfNecessary(parserContext); registerDefaultAccessManagerIfNecessary(parserContext);

View File

@ -1,25 +1,25 @@
package org.springframework.security.config; package org.springframework.security.config;
import org.springframework.security.ui.FilterChainOrder; import java.io.IOException;
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 javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig; import javax.servlet.FilterConfig;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; 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 <tt>Ordered</tt> * Replaces a Spring bean of type "Filter" with a wrapper class which implements the <tt>Ordered</tt>
@ -33,6 +33,7 @@ public class OrderedFilterBeanDefinitionDecorator implements BeanDefinitionDecor
public static final String ATT_AFTER = "after"; public static final String ATT_AFTER = "after";
public static final String ATT_BEFORE = "before"; public static final String ATT_BEFORE = "before";
public static final String ATT_POSITION = "position";
public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder holder, ParserContext parserContext) { public BeanDefinitionHolder decorate(Node node, BeanDefinitionHolder holder, ParserContext parserContext) {
Element elt = (Element)node; Element elt = (Element)node;
@ -56,7 +57,17 @@ public class OrderedFilterBeanDefinitionDecorator implements BeanDefinitionDecor
private String getOrder(Element elt, ParserContext pc) { private String getOrder(Element elt, ParserContext pc) {
String after = elt.getAttribute(ATT_AFTER); String after = elt.getAttribute(ATT_AFTER);
String before = elt.getAttribute(ATT_BEFORE); 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)) { if (StringUtils.hasText(after)) {
return Integer.toString(FilterChainOrder.getOrder(after) + 1); return Integer.toString(FilterChainOrder.getOrder(after) + 1);
} }

View File

@ -43,7 +43,7 @@ public abstract class FilterChainOrder {
private static final Map filterNameToOrder = new LinkedHashMap(); private static final Map filterNameToOrder = new LinkedHashMap();
static { 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("CHANNEL_FILTER", new Integer(CHANNEL_FILTER));
filterNameToOrder.put("CONCURRENT_SESSION_FILTER", new Integer(CONCURRENT_SESSION_FILTER)); filterNameToOrder.put("CONCURRENT_SESSION_FILTER", new Integer(CONCURRENT_SESSION_FILTER));
filterNameToOrder.put("SESSION_CONTEXT_INTEGRATION_FILTER", new Integer(HTTP_SESSION_CONTEXT_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("NTLM_FILTER", new Integer(NTLM_FILTER));
filterNameToOrder.put("FILTER_SECURITY_INTERCEPTOR", new Integer(FILTER_SECURITY_INTERCEPTOR)); filterNameToOrder.put("FILTER_SECURITY_INTERCEPTOR", new Integer(FILTER_SECURITY_INTERCEPTOR));
filterNameToOrder.put("SWITCH_USER_FILTER", new Integer(SWITCH_USER_FILTER)); 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 */ /** Allows filters to be used by name in the XSD file without explicit reference to Java constants */

View File

@ -420,14 +420,19 @@ any-user-service = user-service | jdbc-user-service | ldap-user-service
custom-filter = 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. ## 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 = 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. ## 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} attribute after {named-security-filter}
before = before =
## The filter immediately before which the custom-filter should be placed in the chain ## The filter immediately before which the custom-filter should be placed in the chain
attribute before {named-security-filter} 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"

View File

@ -1173,6 +1173,12 @@
in the chain</xs:documentation> in the chain</xs:documentation>
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
<xs:attribute name="position" type="security:named-security-filter">
<xs:annotation>
<xs:documentation>The explicit position at which the custom-filter should be placed in the
chain. Use if you are replacing a standard filter.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType> </xs:complexType>
</xs:element> </xs:element>
<xs:attributeGroup name="after"> <xs:attributeGroup name="after">
@ -1194,6 +1200,14 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </xs:attributeGroup>
<xs:attributeGroup name="position">
<xs:attribute name="position" use="required" type="security:named-security-filter">
<xs:annotation>
<xs:documentation>The explicit position at which the custom-filter should be placed in the
chain. Use if you are replacing a standard filter.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:simpleType name="named-security-filter"> <xs:simpleType name="named-security-filter">
<xs:restriction base="xs:token"> <xs:restriction base="xs:token">
<xs:enumeration value="FIRST"/> <xs:enumeration value="FIRST"/>
@ -1213,6 +1227,7 @@
<xs:enumeration value="NTLM_FILTER"/> <xs:enumeration value="NTLM_FILTER"/>
<xs:enumeration value="FILTER_SECURITY_INTERCEPTOR"/> <xs:enumeration value="FILTER_SECURITY_INTERCEPTOR"/>
<xs:enumeration value="SWITCH_USER_FILTER"/> <xs:enumeration value="SWITCH_USER_FILTER"/>
<xs:enumeration value="LAST"/>
</xs:restriction> </xs:restriction>
</xs:simpleType> </xs:simpleType>
</xs:schema> </xs:schema>

View File

@ -248,12 +248,17 @@ public class HttpSecurityBeanDefinitionParserTests {
"<b:bean id='userFilter' class='org.springframework.security.util.MockFilter'>" + "<b:bean id='userFilter' class='org.springframework.security.util.MockFilter'>" +
" <custom-filter after='SESSION_CONTEXT_INTEGRATION_FILTER'/>" + " <custom-filter after='SESSION_CONTEXT_INTEGRATION_FILTER'/>" +
"</b:bean>" + "</b:bean>" +
"<b:bean id='userFilter2' class='org.springframework.security.util.MockFilter'/>"); "<b:bean id='userFilter2' class='org.springframework.security.util.MockFilter'>" +
" <custom-filter position='FIRST'/>" +
"</b:bean>" +
"<b:bean id='userFilter3' class='org.springframework.security.util.MockFilter'/>");
List filters = getFilters("/someurl"); List filters = getFilters("/someurl");
assertEquals(12, filters.size()); assertEquals(13, filters.size());
assertTrue(filters.get(1) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator); assertTrue(filters.get(0) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
assertEquals("userFilter", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(1)).getBeanName()); assertTrue(filters.get(2) instanceof OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator);
assertEquals("userFilter", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(2)).getBeanName());
assertEquals("userFilter2", ((OrderedFilterBeanDefinitionDecorator.OrderedFilterDecorator)filters.get(0)).getBeanName());
} }
@Test @Test