Extract a SecurityFilterChain interface and create a default implementation to facilitate other configuration options.

This commit is contained in:
Luke Taylor 2011-07-06 00:12:48 +01:00
parent 2d271666a4
commit f92589f051
10 changed files with 101 additions and 79 deletions

View File

@ -9,6 +9,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
@ -24,6 +25,7 @@ import org.springframework.security.web.context.SecurityContextPersistenceFilter
import org.springframework.security.web.jaasapi.JaasApiIntegrationFilter;
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter;
import org.springframework.security.web.session.SessionManagementFilter;
import org.springframework.security.web.util.AnyRequestMatcher;
public class DefaultFilterChainValidator implements FilterChainProxy.FilterChainValidator {
private final Log logger = LogFactory.getLog(getClass());
@ -34,17 +36,34 @@ public class DefaultFilterChainValidator implements FilterChainProxy.FilterChain
checkFilterStack(filterChain.getFilters());
}
checkPathOrder(new ArrayList<SecurityFilterChain>(fcp.getFilterChains()));
checkForDuplicateMatchers(new ArrayList<SecurityFilterChain>(fcp.getFilterChains()));
}
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
SecurityFilterChain chain = chains.remove(0);
private void checkPathOrder(List<SecurityFilterChain> filterChains) {
// Check that the universal pattern is listed at the end, if at all
Iterator<SecurityFilterChain> chains = filterChains.iterator();
for (SecurityFilterChain test : chains) {
if (chain.getRequestMatcher().equals(test.getRequestMatcher())) {
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the" +
" matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace " +
"elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
while (chains.hasNext()) {
if (((DefaultSecurityFilterChain)chains.next()).getRequestMatcher() instanceof AnyRequestMatcher && chains.hasNext()) {
throw new IllegalArgumentException("A universal match pattern ('/**') is defined " +
" before other patterns in the filter chain, causing them to be ignored. Please check the " +
"ordering in your <security:http> namespace or FilterChainProxy bean configuration");
}
}
}
private void checkForDuplicateMatchers(List<SecurityFilterChain> chains) {
while (chains.size() > 1) {
DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain)chains.remove(0);
for (SecurityFilterChain test : chains) {
if (chain.getRequestMatcher().equals(((DefaultSecurityFilterChain)test).getRequestMatcher())) {
throw new IllegalArgumentException("The FilterChainProxy contains two filter chains using the" +
" matcher " + chain.getRequestMatcher() + ". If you are using multiple <http> namespace " +
"elements, you must use a 'pattern' attribute to define the request patterns to which they apply.");
}
}
}
}

View File

@ -7,6 +7,7 @@ import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -26,7 +27,7 @@ public class FilterChainBeanDefinitionParser implements BeanDefinitionParser {
String requestMatcher = elt.getAttribute(ATT_REQUEST_MATCHER_REF);
String filters = elt.getAttribute(HttpSecurityBeanDefinitionParser.ATT_FILTERS);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterChain.class);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class);
if (StringUtils.hasText(path)) {
Assert.isTrue(!StringUtils.hasText(requestMatcher), "");

View File

@ -21,6 +21,7 @@ import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.Elements;
import org.springframework.security.config.authentication.AuthenticationManagerFactoryBean;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.AnyRequestMatcher;
@ -147,7 +148,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);
}
BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(SecurityFilterChain.class);
BeanDefinitionBuilder filterChainBldr = BeanDefinitionBuilder.rootBeanDefinition(DefaultSecurityFilterChain.class);
filterChainBldr.addConstructorArgValue(filterChainMatcher);
filterChainBldr.addConstructorArgValue(filterChain);

View File

@ -42,7 +42,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
createAppContext()
then:
BeanCreationException e = thrown()
e.cause.cause instanceof IllegalArgumentException
e.cause instanceof IllegalArgumentException
}
def duplicatePatternsAreRejected () {

View File

@ -33,6 +33,7 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@ -67,11 +68,6 @@ public class FilterChainProxyConfigTests {
}
}
@Test(expected=BeanCreationException.class)
public void misplacedUniversalPathShouldBeDetected() throws Exception {
appCtx.getBean("newFilterChainProxyWrongPathOrder", FilterChainProxy.class);
}
@Test
public void normalOperation() throws Exception {
FilterChainProxy filterChainProxy = appCtx.getBean("filterChain", FilterChainProxy.class);
@ -111,9 +107,13 @@ public class FilterChainProxyConfigTests {
FilterChainProxy fcp = appCtx.getBean("sec1235FilterChainProxy", FilterChainProxy.class);
List<SecurityFilterChain> chains = fcp.getFilterChains();
assertEquals("/login*", ((AntPathRequestMatcher)chains.get(0).getRequestMatcher()).getPattern());
assertEquals("/logout", ((AntPathRequestMatcher)chains.get(1).getRequestMatcher()).getPattern());
assertTrue(chains.get(2).getRequestMatcher() instanceof AnyRequestMatcher);
assertEquals("/login*", getPattern(chains.get(0)));
assertEquals("/logout", getPattern(chains.get(1)));
assertTrue(((DefaultSecurityFilterChain)chains.get(2)).getRequestMatcher() instanceof AnyRequestMatcher);
}
private String getPattern(SecurityFilterChain chain) {
return ((AntPathRequestMatcher)((DefaultSecurityFilterChain)chain).getRequestMatcher()).getPattern();
}
private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {

View File

@ -98,8 +98,11 @@
<sec:filter-chain pattern="/some/other/path/**" filters="sif,mockFilter,mockFilter2"/>
</util:list>
</constructor-arg>
<property name="filterChainValidator" ref="fcv" />
</bean>
<bean id="fcv" class="org.springframework.security.config.http.DefaultFilterChainValidator" />
<bean id="newFilterChainProxyRegex" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="regex">
<sec:filter-chain pattern="\A/foo/.*\Z" filters="mockFilter"/>
@ -124,7 +127,7 @@
<bean id="newFilterChainProxyNonNamespace" class="org.springframework.security.web.FilterChainProxy">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.SecurityFilterChain">
<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg ref="fooMatcher"/>
<constructor-arg>
<list>
@ -132,7 +135,7 @@
</list>
</constructor-arg>
</bean>
<bean class="org.springframework.security.web.SecurityFilterChain">
<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg>
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
<constructor-arg value="/some/other/path/**"/>
@ -146,7 +149,7 @@
</list>
</constructor-arg>
</bean>
<bean class="org.springframework.security.web.SecurityFilterChain">
<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg>
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
<constructor-arg value="/do/not/filter*"/>
@ -156,7 +159,7 @@
<list />
</constructor-arg>
</bean>
<bean class="org.springframework.security.web.SecurityFilterChain">
<bean class="org.springframework.security.web.DefaultSecurityFilterChain">
<constructor-arg>
<bean class="org.springframework.security.web.util.AntPathRequestMatcher">
<constructor-arg value="/**"/>

View File

@ -0,0 +1,45 @@
package org.springframework.security.web;
import org.springframework.security.web.util.RequestMatcher;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
/**
* Standard implementation of {@code SecurityFilterChain}.
*
* @author Luke Taylor
*
* @since 3.1
*/
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<Filter>(filters);
}
public RequestMatcher getRequestMatcher() {
return requestMatcher;
}
public List<Filter> getFilters() {
return filters;
}
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
@Override
public String toString() {
return "[ " + requestMatcher + ", " + filters + "]";
}
}

View File

@ -142,7 +142,6 @@ public class FilterChainProxy extends GenericFilterBean {
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
checkPathOrder();
}
@Override
@ -219,10 +218,8 @@ public class FilterChainProxy extends GenericFilterBean {
filterChains = new ArrayList<SecurityFilterChain>(filterChainMap.size());
for (Map.Entry<RequestMatcher,List<Filter>> entry : filterChainMap.entrySet()) {
filterChains.add(new SecurityFilterChain(entry.getKey(), entry.getValue()));
filterChains.add(new DefaultSecurityFilterChain(entry.getKey(), entry.getValue()));
}
checkPathOrder();
}
/**
@ -238,25 +235,12 @@ public class FilterChainProxy extends GenericFilterBean {
LinkedHashMap<RequestMatcher, List<Filter>> map = new LinkedHashMap<RequestMatcher, List<Filter>>();
for (SecurityFilterChain chain : filterChains) {
map.put(chain.getRequestMatcher(), chain.getFilters());
map.put(((DefaultSecurityFilterChain)chain).getRequestMatcher(), chain.getFilters());
}
return map;
}
private void checkPathOrder() {
// Check that the universal pattern is listed at the end, if at all
Iterator<SecurityFilterChain> chains = filterChains.iterator();
while(chains.hasNext()) {
if ((chains.next().getRequestMatcher() instanceof AnyRequestMatcher && chains.hasNext())) {
throw new IllegalArgumentException("A universal match pattern ('/**') is defined " +
" before other patterns in the filter chain, causing them to be ignored. Please check the " +
"ordering in your <security:http> namespace or FilterChainProxy bean configuration");
}
}
}
/**
* @return the list of {@code SecurityFilterChain}s which will be matched against and
* applied to incoming requests.

View File

@ -1,54 +1,23 @@
package org.springframework.security.web;
import org.springframework.security.web.util.RequestMatcher;
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.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
/**
* Bean which defines a filter chain which is capable of being matched against an {@code HttpServletRequest}.
* Defines a filter chain which is capable of being matched against an {@code HttpServletRequest}.
* in order to decide whether it applies to that request.
* <p>
* Used to configure a {@code FilterChainProxy}.
*
*
* @author Luke Taylor
*
* @since 3.1
*/
public final class SecurityFilterChain {
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public interface SecurityFilterChain {
public SecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
boolean matches(HttpServletRequest request);
public SecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<Filter>(filters);
}
public RequestMatcher getRequestMatcher() {
return requestMatcher;
}
public List<Filter> getFilters() {
return filters;
}
public boolean matches(HttpServletRequest request) {
return requestMatcher.matches(request);
}
@Override
public String toString() {
return "[ " + requestMatcher + ", " + filters + "]";
}
List<Filter> getFilters();
}

View File

@ -47,7 +47,7 @@ public class FilterChainProxyTests {
return null;
}
}).when(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
fcp = new FilterChainProxy(new SecurityFilterChain(matcher, Arrays.asList(filter)));
fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, Arrays.asList(filter)));
fcp.setFilterChainValidator(mock(FilterChainProxy.FilterChainValidator.class));
request = new MockHttpServletRequest();
request.setServletPath("/path");
@ -94,7 +94,7 @@ public class FilterChainProxyTests {
@Test
public void originalFilterChainIsInvokedIfMatchingSecurityChainIsEmpty() throws Exception {
List<Filter> noFilters = Collections.emptyList();
fcp = new FilterChainProxy(new SecurityFilterChain(matcher, noFilters));
fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, noFilters));
when(matcher.matches(any(HttpServletRequest.class))).thenReturn(true);
fcp.doFilter(request, response, chain);
@ -137,7 +137,7 @@ public class FilterChainProxyTests {
@Test
public void bothWrappersAreResetWithNestedFcps() throws Exception {
HttpFirewall fw = mock(HttpFirewall.class);
FilterChainProxy firstFcp = new FilterChainProxy(new SecurityFilterChain(matcher, fcp));
FilterChainProxy firstFcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher, fcp));
firstFcp.setFirewall(fw);
fcp.setFirewall(fw);
FirewalledRequest firstFwr = mock(FirewalledRequest.class, "firstFwr");