瀏覽代碼

SEC-1584: Backport to 2.0.x branch of request firewalling (normalization checks and path-parameter stripping from servletPath and pathInfo).

Luke Taylor 15 年之前
父節點
當前提交
8f6ddb0f17

+ 24 - 18
core/pom.xml

@@ -47,27 +47,27 @@
             <artifactId>spring-mock</artifactId>
             <artifactId>spring-mock</artifactId>
             <optional>true</optional>
             <optional>true</optional>
         </dependency>
         </dependency>
-	    <dependency>
-	        <groupId>org.aspectj</groupId>
-		<artifactId>aspectjrt</artifactId>
-		<optional>true</optional>
-	    </dependency>
-	    <dependency>
-	        <groupId>org.aspectj</groupId>
-		<artifactId>aspectjweaver</artifactId>
-		<optional>true</optional>
-	    </dependency>	    
+        <dependency>
+            <groupId>org.aspectj</groupId>
+        <artifactId>aspectjrt</artifactId>
+        <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+        <artifactId>aspectjweaver</artifactId>
+        <optional>true</optional>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>org.springframework.ldap</groupId>
             <groupId>org.springframework.ldap</groupId>
             <artifactId>spring-ldap</artifactId>
             <artifactId>spring-ldap</artifactId>
             <optional>true</optional>
             <optional>true</optional>
         </dependency>
         </dependency>
-	    <dependency>
-	        <groupId>cglib</groupId>
-		    <artifactId>cglib-nodep</artifactId>
-		    <scope>test</scope>
-		    <optional>true</optional>
-	    </dependency>	    
+        <dependency>
+            <groupId>cglib</groupId>
+            <artifactId>cglib-nodep</artifactId>
+            <scope>test</scope>
+            <optional>true</optional>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>net.sf.ehcache</groupId>
             <groupId>net.sf.ehcache</groupId>
             <artifactId>ehcache</artifactId>
             <artifactId>ehcache</artifactId>
@@ -95,7 +95,7 @@
             <artifactId>jaxen</artifactId>
             <artifactId>jaxen</artifactId>
             <version>1.1.1</version>
             <version>1.1.1</version>
             <optional>true</optional>
             <optional>true</optional>
-        </dependency>        
+        </dependency>
         <dependency>
         <dependency>
             <groupId>javax.servlet</groupId>
             <groupId>javax.servlet</groupId>
             <artifactId>servlet-api</artifactId>
             <artifactId>servlet-api</artifactId>
@@ -135,13 +135,19 @@
             <version>1.0.1</version>
             <version>1.0.1</version>
             <scope>test</scope>
             <scope>test</scope>
         </dependency>
         </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.8.5</version>
+            <scope>test</scope>
+        </dependency>
         <dependency>
         <dependency>
             <groupId>log4j</groupId>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
             <artifactId>log4j</artifactId>
             <optional>true</optional>
             <optional>true</optional>
         </dependency>
         </dependency>
     </dependencies>
     </dependencies>
-    
+
     <build>
     <build>
         <plugins>
         <plugins>
             <plugin>
             <plugin>

+ 67 - 0
core/src/main/java/org/springframework/security/firewall/DefaultHttpFirewall.java

@@ -0,0 +1,67 @@
+package org.springframework.security.firewall;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Default implementation which wraps requests in order to provide consistent values of the {@code servletPath} and
+ * {@code pathInfo}, which do not contain path parameters (as defined in
+ * <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>). Different servlet containers
+ * interpret the servlet spec differently as to how path parameters are treated and it is possible they might be added
+ * in order to bypass particular security constraints. When using this implementation, they will be removed for all
+ * requests as the request passes through the security filter chain. Note that this means that any segments in the
+ * decoded path which contain a semi-colon, will have the part following the semi-colon removed for
+ * request matching. Your application should not contain any valid paths which contain semi-colons.
+ * <p>
+ * If any un-normalized paths are found (containing directory-traversal character sequences), the request will be
+ * rejected immediately. Most containers normalize the paths before performing the servlet-mapping, but again this is
+ * not guaranteed by the servlet spec.
+ *
+ * @author Luke Taylor
+ */
+public class DefaultHttpFirewall implements HttpFirewall {
+
+    public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
+        FirewalledRequest fwr = new RequestWrapper(request);
+
+        if (!isNormalized(fwr.getServletPath()) || !isNormalized(fwr.getPathInfo())) {
+            throw new RequestRejectedException("Un-normalized paths are not supported: " + fwr.getServletPath() +
+                (fwr.getPathInfo() != null ? fwr.getPathInfo() : ""));
+        }
+
+        return fwr;
+    }
+
+    public HttpServletResponse getFirewalledResponse(HttpServletResponse response) {
+        return response;
+    }
+
+    /**
+     * Checks whether a path is normalized (doesn't contain path traversal sequences like "./", "/../" or "/.")
+     *
+     * @param path the path to test
+     * @return true if the path doesn't contain any path-traversal character sequences.
+     */
+    private boolean isNormalized(String path) {
+        if (path == null) {
+            return true;
+        }
+
+        for (int j = path.length(); j > 0;) {
+            int i = path.lastIndexOf('/', j - 1);
+            int gap = j - i;
+
+            if (gap == 2 && path.charAt(i+1) == '.') {
+                // ".", "/./" or "/."
+                return false;
+            } else if (gap == 3 && path.charAt(i+1) == '.'&& path.charAt(i+2) == '.') {
+                return false;
+            }
+
+            j = i;
+        }
+
+        return true;
+    }
+
+}

+ 34 - 0
core/src/main/java/org/springframework/security/firewall/FirewalledRequest.java

@@ -0,0 +1,34 @@
+package org.springframework.security.firewall;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/**
+ * Request wrapper which is returned by the {@code HttpFirewall} interface.
+ * <p>
+ * The only difference is the {@code reset} method which allows some
+ * or all of the state to be reset by the {@code FilterChainProxy} when the
+ * request leaves the security filter chain.
+ *
+ * @author Luke Taylor
+ */
+public abstract class FirewalledRequest extends HttpServletRequestWrapper {
+    /**
+     * Constructs a request object wrapping the given request.
+     *
+     * @throws IllegalArgumentException if the request is null
+     */
+    public FirewalledRequest(HttpServletRequest request) {
+        super(request);
+    }
+
+    /**
+     * This method will be called once the request has passed through the
+     * security filter chain, when it is about to proceed to the application
+     * proper.
+     * <p>
+     * An implementation can thus choose to modify the state of the request
+     * for the security infrastructure, while still maintaining the
+     */
+    public abstract void reset();
+}

+ 32 - 0
core/src/main/java/org/springframework/security/firewall/HttpFirewall.java

@@ -0,0 +1,32 @@
+package org.springframework.security.firewall;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Interface which can be used to reject potentially dangerous requests and/or wrap them to
+ * control their behaviour.
+ * <p>
+ * The implementation is injected into the {@code FilterChainProxy} and will be invoked before
+ * sending any request through the filter chain. It can also provide a response wrapper if the response
+ * behaviour should also be restricted.
+ *
+ * @author Luke Taylor
+ */
+public interface HttpFirewall {
+
+    /**
+     * Provides the request object which will be passed through the filter chain.
+     *
+     * @throws RequestRejectedException if the request should be rejected immediately
+     */
+    FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException;
+
+    /**
+     * Provides the response which will be passed through the filter chain.
+     *
+     * @param response the original response
+     * @return either the original response or a replacement/wrapper.
+     */
+    HttpServletResponse getFirewalledResponse(HttpServletResponse response);
+}

+ 10 - 0
core/src/main/java/org/springframework/security/firewall/RequestRejectedException.java

@@ -0,0 +1,10 @@
+package org.springframework.security.firewall;
+
+/**
+ * @author Luke Taylor
+ */
+public class RequestRejectedException extends RuntimeException {
+    public RequestRejectedException(String message) {
+        super(message);
+    }
+}

+ 98 - 0
core/src/main/java/org/springframework/security/firewall/RequestWrapper.java

@@ -0,0 +1,98 @@
+package org.springframework.security.firewall;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+/**
+ * Request wrapper which ensures values of {@code servletPath} and {@code pathInfo} are returned which are suitable for
+ * pattern matching against. It strips out path parameters and extra consecutive '/' characters.
+ *
+ * <h3>Path Parameters</h3>
+ * Parameters (as defined in <a href="http://www.ietf.org/rfc/rfc2396.txt">RFC 2396</a>) are stripped from the path
+ * segments of the {@code servletPath} and {@code pathInfo} values of the request.
+ * <p>
+ * The parameter sequence is demarcated by a semi-colon, so each segment is checked for the occurrence of a ";"
+ * character and truncated at that point if it is present.
+ * <p>
+ * The behaviour differs between servlet containers in how they interpret the servlet spec, which
+ * does not clearly state what the behaviour should be. For consistency, we make sure they are always removed, to
+ * avoid the risk of URL matching rules being bypassed by the malicious addition of parameters to the path component.
+ *
+ * @author Luke Taylor
+ */
+final class RequestWrapper extends FirewalledRequest {
+    private final String strippedServletPath;
+    private final String strippedPathInfo;
+    private boolean stripPaths = true;
+
+    public RequestWrapper(HttpServletRequest request) {
+        super(request);
+        strippedServletPath = strip(request.getServletPath());
+        String pathInfo = strip(request.getPathInfo());
+        if (pathInfo != null && pathInfo.length() == 0) {
+            pathInfo = null;
+        }
+        strippedPathInfo = pathInfo;
+    }
+
+    /**
+     * Removes path parameters from each path segment in the supplied path and truncates sequences of multiple '/'
+     * characters to a single '/'.
+     *
+     * @param path either the {@code servletPath} and {@code pathInfo} from the original request
+     *
+     * @return the supplied value, with path parameters removed and sequences of multiple '/' characters truncated,
+     *  or null if the supplied path was null.
+     */
+    private String strip(String path) {
+        if (path == null) {
+            return null;
+        }
+
+        int scIndex = path.indexOf(';');
+
+        if (scIndex < 0) {
+            int doubleSlashIndex = path.indexOf("//");
+            if (doubleSlashIndex < 0) {
+                // Most likely case, no parameters in any segment and no '//', so no stripping required
+                return path;
+            }
+        }
+
+        StringTokenizer st = new StringTokenizer(path, "/");
+        StringBuilder stripped = new StringBuilder(path.length());
+
+        if (path.charAt(0) == '/') {
+            stripped.append('/');
+        }
+
+        while(st.hasMoreTokens()) {
+            String segment = st.nextToken();
+            scIndex = segment.indexOf(';');
+
+            if (scIndex >= 0) {
+                segment = segment.substring(0, scIndex);
+            }
+            stripped.append(segment).append('/');
+        }
+
+        // Remove the trailing slash if the original path didn't have one
+        if (path.charAt(path.length() - 1) != '/') {
+            stripped.deleteCharAt(stripped.length() - 1);
+        }
+
+        return stripped.toString();
+    }
+
+    public String getPathInfo() {
+        return stripPaths ? strippedPathInfo : super.getPathInfo();
+    }
+
+    public String getServletPath() {
+        return stripPaths ? strippedServletPath : super.getServletPath();
+    }
+
+    public void reset() {
+        this.stripPaths = false;
+    }
+}

+ 26 - 4
core/src/main/java/org/springframework/security/util/FilterChainProxy.java

@@ -21,11 +21,17 @@ import org.springframework.beans.BeansException;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContextAware;
 import org.springframework.context.ApplicationContextAware;
+import org.springframework.security.firewall.DefaultHttpFirewall;
+import org.springframework.security.firewall.FirewalledRequest;
+import org.springframework.security.firewall.HttpFirewall;
 import org.springframework.security.intercept.web.*;
 import org.springframework.security.intercept.web.*;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.DelegatingFilterProxy;
 import org.springframework.web.filter.DelegatingFilterProxy;
 
 
 import javax.servlet.*;
 import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
 import java.io.IOException;
 import java.io.IOException;
 import java.util.*;
 import java.util.*;
 
 
@@ -34,7 +40,7 @@ import java.util.*;
  * Delegates <code>Filter</code> requests to a list of Spring-managed beans.
  * Delegates <code>Filter</code> requests to a list of Spring-managed beans.
  * As of version 2.0, you shouldn't need to explicitly configure a <tt>FilterChainProxy</tt> bean in your application
  * As of version 2.0, you shouldn't need to explicitly configure a <tt>FilterChainProxy</tt> bean in your application
  * context unless you need very fine control over the filter chain contents. Most cases should be adequately covered
  * context unless you need very fine control over the filter chain contents. Most cases should be adequately covered
- * by the default <tt>&lt;security:http /&gt</tt> namespace configuration options.
+ * by the default <tt>&lt;security:http /&gt;</tt> namespace configuration options.
  *
  *
  * <p>The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
  * <p>The <code>FilterChainProxy</code> is loaded via a standard Spring {@link DelegatingFilterProxy} declaration in
  * <code>web.xml</code>. <code>FilterChainProxy</code> will then pass {@link #init(FilterConfig)}, {@link #destroy()}
  * <code>web.xml</code>. <code>FilterChainProxy</code> will then pass {@link #init(FilterConfig)}, {@link #destroy()}
@@ -109,6 +115,7 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
     private UrlMatcher matcher = new AntUrlPathMatcher();
     private UrlMatcher matcher = new AntUrlPathMatcher();
     private boolean stripQueryStringFromUrls = true;
     private boolean stripQueryStringFromUrls = true;
     private DefaultFilterInvocationDefinitionSource fids;
     private DefaultFilterInvocationDefinitionSource fids;
+    private HttpFirewall firewall = new DefaultHttpFirewall();
 
 
     //~ Methods ========================================================================================================
     //~ Methods ========================================================================================================
 
 
@@ -154,10 +161,13 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
         }
         }
     }
     }
 
 
-    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+    public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain)
             throws IOException, ServletException {
             throws IOException, ServletException {
 
 
-        FilterInvocation fi = new FilterInvocation(request, response, chain);
+        FirewalledRequest fwRequest = firewall.getFirewalledRequest((HttpServletRequest) servletRequest);
+        HttpServletResponse fwResponse = firewall.getFirewalledResponse((HttpServletResponse) response);
+
+        FilterInvocation fi = new FilterInvocation(fwRequest, fwResponse, chain);
         List filters = getFilters(fi.getRequestUrl());
         List filters = getFilters(fi.getRequestUrl());
 
 
         if (filters == null || filters.size() == 0) {
         if (filters == null || filters.size() == 0) {
@@ -166,7 +176,7 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
                         filters == null ? " has no matching filters" : " has an empty filter list");
                         filters == null ? " has no matching filters" : " has an empty filter list");
             }
             }
 
 
-            chain.doFilter(request, response);
+            chain.doFilter(fwRequest, response);
 
 
             return;
             return;
         }
         }
@@ -374,6 +384,8 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
                     logger.debug(fi.getRequestUrl()
                     logger.debug(fi.getRequestUrl()
                         + " reached end of additional filter chain; proceeding with original chain");
                         + " reached end of additional filter chain; proceeding with original chain");
                 }
                 }
+                // Deactivate path stripping as we exit the security filter chain
+                resetWrapper(request);
 
 
                 fi.getChain().doFilter(request, response);
                 fi.getChain().doFilter(request, response);
             } else {
             } else {
@@ -390,6 +402,16 @@ public class FilterChainProxy implements Filter, InitializingBean, ApplicationCo
                nextFilter.doFilter(request, response, this);
                nextFilter.doFilter(request, response, this);
             }
             }
         }
         }
+
+        private void resetWrapper(ServletRequest request) {
+            while (request instanceof ServletRequestWrapper) {
+                if (request instanceof FirewalledRequest) {
+                    ((FirewalledRequest)request).reset();
+                    break;
+                }
+                request = ((ServletRequestWrapper)request).getRequest();
+            }
+        }
     }
     }
 
 
 }
 }

+ 44 - 0
core/src/test/java/org/springframework/security/firewall/DefaultHttpFirewallTests.java

@@ -0,0 +1,44 @@
+package org.springframework.security.firewall;
+
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+/**
+ * @author Luke Taylor
+ */
+public class DefaultHttpFirewallTests {
+    public String[] unnormalizedPaths = {
+            "/..",
+            "/./path/",
+            "/path/path/.",
+            "/path/path//.",
+            "./path/../path//.",
+            "./path",
+            ".//path",
+            "."
+    };
+
+    @Test
+    public void unnormalizedPathsAreRejected() throws Exception {
+        DefaultHttpFirewall fw = new DefaultHttpFirewall();
+
+        MockHttpServletRequest request;
+        for (String path : unnormalizedPaths) {
+            request = new MockHttpServletRequest();
+            request.setServletPath(path);
+            try {
+                fw.getFirewalledRequest(request);
+                fail(path + " is un-normalized");
+            } catch (RequestRejectedException expected) {
+            }
+            request.setPathInfo(path);
+            try {
+                fw.getFirewalledRequest(request);
+                fail(path + " is un-normalized");
+            } catch (RequestRejectedException expected) {
+            }
+        }
+    }
+}

+ 62 - 0
core/src/test/java/org/springframework/security/firewall/RequestWrapperTests.java

@@ -0,0 +1,62 @@
+package org.springframework.security.firewall;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import java.util.*;
+
+/**
+ * @author Luke Taylor
+ */
+public class RequestWrapperTests {
+    private static Map<String, String> testPaths = new LinkedHashMap<String,String>();
+
+    @BeforeClass
+    // Some of these may be unrealistic values, but we can't be sure because of the
+    // inconsistency in the spec.
+    public static void createTestMap() {
+        testPaths.put("/path1;x=y;z=w/path2;x=y/path3;x=y", "/path1/path2/path3");
+        testPaths.put("/path1;x=y/path2;x=y/", "/path1/path2/");
+        testPaths.put("/path1//path2/", "/path1/path2/");
+        testPaths.put("//path1/path2//", "/path1/path2/");
+        testPaths.put(";x=y;z=w", "");
+    }
+
+    @Test
+    public void pathParametersAreRemovedFromServletPath() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+
+        for (Map.Entry<String,String> entry : testPaths.entrySet()) {
+            String path = entry.getKey();
+            String expectedResult = entry.getValue();
+            request.setServletPath(path);
+            RequestWrapper wrapper = new RequestWrapper(request);
+            assertEquals(expectedResult, wrapper.getServletPath());
+            wrapper.reset();
+            assertEquals(path, wrapper.getServletPath());
+        }
+    }
+
+    @Test
+    public void pathParametersAreRemovedFromPathInfo() {
+        MockHttpServletRequest request = new MockHttpServletRequest();
+
+        for (Map.Entry<String,String> entry : testPaths.entrySet()) {
+            String path = entry.getKey();
+            String expectedResult = entry.getValue();
+            // Should be null when stripped value is empty
+            if (expectedResult.length() == 0) {
+                expectedResult = null;
+            }
+            request.setPathInfo(path);
+            RequestWrapper wrapper = new RequestWrapper(request);
+            assertEquals(expectedResult, wrapper.getPathInfo());
+            wrapper.reset();
+            assertEquals(path, wrapper.getPathInfo());
+        }
+    }
+
+}

+ 237 - 0
core/src/test/java/org/springframework/security/util/FilterChainProxyConfigTests.java

@@ -0,0 +1,237 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.util;
+
+import org.springframework.security.ConfigAttribute;
+import org.springframework.security.ConfigAttributeDefinition;
+import org.springframework.security.MockFilterConfig;
+import org.springframework.security.context.HttpSessionContextIntegrationFilter;
+import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
+import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
+import org.springframework.security.intercept.web.RequestKey;
+import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
+
+import org.springframework.beans.factory.BeanCreationException;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import org.junit.After;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+
+/**
+ * Tests {@link FilterChainProxy}.
+ *
+ * @author Carlos Sanchez
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class FilterChainProxyConfigTests {
+    private ClassPathXmlApplicationContext appCtx;
+
+    //~ Methods ========================================================================================================
+
+    @Before
+    public void loadContext() {
+        appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
+    }
+
+    @After
+    public void closeContext() {
+        if (appCtx != null) {
+            appCtx.close();
+        }
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testDetectsFilterInvocationDefinitionSourceThatDoesNotReturnAllConfigAttributes() throws Exception {
+        FilterChainProxy filterChainProxy = new FilterChainProxy();
+        filterChainProxy.setApplicationContext(new StaticApplicationContext());
+
+        filterChainProxy.setFilterInvocationDefinitionSource(new MockFilterInvocationDefinitionSource(false, false));
+        filterChainProxy.afterPropertiesSet();
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testDetectsIfConfigAttributeDoesNotReturnValueForGetAttributeMethod() throws Exception {
+        FilterChainProxy filterChainProxy = new FilterChainProxy();
+        filterChainProxy.setApplicationContext(new StaticApplicationContext());
+
+        ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new MockConfigAttribute());
+
+        LinkedHashMap map = new LinkedHashMap();
+        map.put(new RequestKey("/**"), cad);
+        DefaultFilterInvocationDefinitionSource fids =
+                new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), map);
+
+        filterChainProxy.setFilterInvocationDefinitionSource(fids);
+
+        filterChainProxy.afterPropertiesSet();
+        filterChainProxy.init(new MockFilterConfig());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDetectsMissingFilterInvocationDefinitionSource() throws Exception {
+        FilterChainProxy filterChainProxy = new FilterChainProxy();
+        filterChainProxy.setApplicationContext(new StaticApplicationContext());
+
+        filterChainProxy.afterPropertiesSet();
+    }
+
+    @Test
+    public void testDoNotFilter() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
+        MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setServletPath("/do/not/filter/somefile.html");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(true);
+
+        filterChainProxy.doFilter(request, response, chain);
+        assertFalse(filter.isWasInitialized());
+        assertFalse(filter.isWasDoFiltered());
+        assertFalse(filter.isWasDestroyed());
+    }
+
+    @Test
+    public void misplacedUniversalPathShouldBeDetected() throws Exception {
+        try {
+            appCtx.getBean("newFilterChainProxyWrongPathOrder", FilterChainProxy.class);
+            fail("Expected BeanCreationException");
+        } catch (BeanCreationException expected) {
+        }
+    }
+
+    @Test
+    public void normalOperation() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
+        doNormalOperation(filterChainProxy);
+    }
+
+    @Test
+    public void proxyPathWithoutLowerCaseConversionShouldntMatchDifferentCasePath() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChainNonLowerCase", FilterChainProxy.class);
+        assertNull(filterChainProxy.getFilters("/some/other/path/blah"));
+    }
+
+    @Test
+    public void normalOperationWithNewConfig() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxy", FilterChainProxy.class);
+        checkPathAndFilterOrder(filterChainProxy);
+        doNormalOperation(filterChainProxy);
+    }
+
+    @Test
+    public void normalOperationWithNewConfigRegex() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyRegex", FilterChainProxy.class);
+        checkPathAndFilterOrder(filterChainProxy);
+        doNormalOperation(filterChainProxy);
+    }
+
+    @Test
+    public void normalOperationWithNewConfigNonNamespace() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNonNamespace", FilterChainProxy.class);
+        checkPathAndFilterOrder(filterChainProxy);
+        doNormalOperation(filterChainProxy);
+    }
+
+    @Test
+    public void pathWithNoMatchHasNoFilters() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNoDefaultPath", FilterChainProxy.class);
+        assertEquals(null, filterChainProxy.getFilters("/nomatch"));
+    }
+
+    @Test
+    public void urlStrippingPropertyIsRespected() throws Exception {
+        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNoDefaultPath", FilterChainProxy.class);
+
+        // Should only match if we are stripping the query string
+        String url = "/blah.bar?x=something";
+        assertNotNull(filterChainProxy.getFilters(url));
+        assertEquals(2, filterChainProxy.getFilters(url).size());
+        filterChainProxy.setStripQueryStringFromUrls(false);
+        assertNull(filterChainProxy.getFilters(url));
+    }
+
+    private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
+        List filters = filterChainProxy.getFilters("/foo/blah");
+        assertEquals(1, filters.size());
+        assertTrue(filters.get(0) instanceof MockFilter);
+
+        filters = filterChainProxy.getFilters("/some/other/path/blah");
+        assertNotNull(filters);
+        assertEquals(3, filters.size());
+        assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
+        assertTrue(filters.get(1) instanceof MockFilter);
+        assertTrue(filters.get(2) instanceof MockFilter);
+
+        filters = filterChainProxy.getFilters("/do/not/filter");
+        assertEquals(0, filters.size());
+
+        filters = filterChainProxy.getFilters("/another/nonspecificmatch");
+        assertEquals(3, filters.size());
+        assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
+        assertTrue(filters.get(1) instanceof AuthenticationProcessingFilter);
+        assertTrue(filters.get(2) instanceof MockFilter);
+    }
+
+    private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
+        MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
+        assertFalse(filter.isWasInitialized());
+        assertFalse(filter.isWasDoFiltered());
+        assertFalse(filter.isWasDestroyed());
+
+        filterChainProxy.init(new MockFilterConfig());
+        assertTrue(filter.isWasInitialized());
+        assertFalse(filter.isWasDoFiltered());
+        assertFalse(filter.isWasDestroyed());
+
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        request.setServletPath("/foo/secure/super/somefile.html");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(true);
+
+        filterChainProxy.doFilter(request, response, chain);
+        assertTrue(filter.isWasInitialized());
+        assertTrue(filter.isWasDoFiltered());
+        assertFalse(filter.isWasDestroyed());
+
+        request.setServletPath("/a/path/which/doesnt/match/any/filter.html");
+        filterChainProxy.doFilter(request, response, chain);
+
+        filterChainProxy.destroy();
+        assertTrue(filter.isWasInitialized());
+        assertTrue(filter.isWasDoFiltered());
+        assertTrue(filter.isWasDestroyed());
+    }
+
+    //~ Inner Classes ==================================================================================================
+
+    private class MockConfigAttribute implements ConfigAttribute {
+        public String getAttribute() {
+            return null;
+        }
+    }
+}

+ 66 - 200
core/src/test/java/org/springframework/security/util/FilterChainProxyTests.java

@@ -1,237 +1,103 @@
-/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
 package org.springframework.security.util;
 package org.springframework.security.util;
 
 
-import org.springframework.security.ConfigAttribute;
-import org.springframework.security.ConfigAttributeDefinition;
-import org.springframework.security.MockFilterConfig;
-import org.springframework.security.context.HttpSessionContextIntegrationFilter;
-import org.springframework.security.intercept.web.MockFilterInvocationDefinitionSource;
-import org.springframework.security.intercept.web.DefaultFilterInvocationDefinitionSource;
-import org.springframework.security.intercept.web.RequestKey;
-import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
-
-import org.springframework.beans.factory.BeanCreationException;
-import org.springframework.context.support.ClassPathXmlApplicationContext;
-import org.springframework.context.support.StaticApplicationContext;
-import org.springframework.mock.web.MockHttpServletRequest;
-import org.springframework.mock.web.MockHttpServletResponse;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
 
 
-import org.junit.After;
-import static org.junit.Assert.*;
 import org.junit.Before;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.security.firewall.FirewalledRequest;
 
 
-import java.util.LinkedHashMap;
-import java.util.List;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.*;
 
 
 /**
 /**
- * Tests {@link FilterChainProxy}.
- *
- * @author Carlos Sanchez
- * @author Ben Alex
- * @version $Id$
+ * @author Luke Taylor
  */
  */
+@SuppressWarnings({"unchecked"})
 public class FilterChainProxyTests {
 public class FilterChainProxyTests {
-    private ClassPathXmlApplicationContext appCtx;
-
-    //~ Methods ========================================================================================================
+    private FilterChainProxy fcp;
+    private MockHttpServletRequest request;
+    private MockHttpServletResponse response;
+    private FilterChain chain;
+    private Filter filter;
 
 
     @Before
     @Before
-    public void loadContext() {
-        appCtx = new ClassPathXmlApplicationContext("org/springframework/security/util/filtertest-valid.xml");
-    }
-
-    @After
-    public void closeContext() {
-        if (appCtx != null) {
-            appCtx.close();
-        }
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDetectsFilterInvocationDefinitionSourceThatDoesNotReturnAllConfigAttributes() throws Exception {
-        FilterChainProxy filterChainProxy = new FilterChainProxy();
-        filterChainProxy.setApplicationContext(new StaticApplicationContext());
-
-        filterChainProxy.setFilterInvocationDefinitionSource(new MockFilterInvocationDefinitionSource(false, false));
-        filterChainProxy.afterPropertiesSet();
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testDetectsIfConfigAttributeDoesNotReturnValueForGetAttributeMethod() throws Exception {
-        FilterChainProxy filterChainProxy = new FilterChainProxy();
-        filterChainProxy.setApplicationContext(new StaticApplicationContext());
-
-        ConfigAttributeDefinition cad = new ConfigAttributeDefinition(new MockConfigAttribute());
-
+    public void setup() throws Exception {
+        fcp = new FilterChainProxy();
+        filter = mock(Filter.class);
+        doAnswer(new Answer() {
+                    public Object answer(InvocationOnMock inv) throws Throwable {
+                        Object[] args = inv.getArguments();
+                        FilterChain fc = (FilterChain) args[2];
+                        fc.doFilter((HttpServletRequest) args[0], (HttpServletResponse) args[1]);
+                        return null;
+                    }
+                }).when(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
         LinkedHashMap map = new LinkedHashMap();
         LinkedHashMap map = new LinkedHashMap();
-        map.put(new RequestKey("/**"), cad);
-        DefaultFilterInvocationDefinitionSource fids =
-                new DefaultFilterInvocationDefinitionSource(new AntUrlPathMatcher(), map);
-
-        filterChainProxy.setFilterInvocationDefinitionSource(fids);
-
-        filterChainProxy.afterPropertiesSet();
-        filterChainProxy.init(new MockFilterConfig());
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testDetectsMissingFilterInvocationDefinitionSource() throws Exception {
-        FilterChainProxy filterChainProxy = new FilterChainProxy();
-        filterChainProxy.setApplicationContext(new StaticApplicationContext());
-
-        filterChainProxy.afterPropertiesSet();
+        map.put("/match", Arrays.asList(filter));
+        fcp.setFilterChainMap(map);
+        request = new MockHttpServletRequest();
+        request.setServletPath("/match");
+        response = new MockHttpServletResponse();
+        chain = mock(FilterChain.class);
     }
     }
 
 
     @Test
     @Test
-    public void testDoNotFilter() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
-        MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setServletPath("/do/not/filter/somefile.html");
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain(true);
-
-        filterChainProxy.doFilter(request, response, chain);
-        assertFalse(filter.isWasInitialized());
-        assertFalse(filter.isWasDoFiltered());
-        assertFalse(filter.isWasDestroyed());
+    public void toStringCallSucceeds() throws Exception {
+        fcp.afterPropertiesSet();
+        fcp.toString();
     }
     }
 
 
     @Test
     @Test
-    public void misplacedUniversalPathShouldBeDetected() throws Exception {
-        try {
-            appCtx.getBean("newFilterChainProxyWrongPathOrder", FilterChainProxy.class);
-            fail("Expected BeanCreationException");
-        } catch (BeanCreationException expected) {
-        }
-    }
+    public void securityFilterChainIsNotInvokedIfMatchFails() throws Exception {
+        request.setServletPath("/nomatch");
+        fcp.doFilter(request, response, chain);
+        assertEquals(1, fcp.getFilterChainMap().size());
 
 
-    @Test
-    public void normalOperation() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChain", FilterChainProxy.class);
-        doNormalOperation(filterChainProxy);
+        verifyZeroInteractions(filter);
+        // The actual filter chain should be invoked though
+        verify(chain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
     }
     }
 
 
     @Test
     @Test
-    public void proxyPathWithoutLowerCaseConversionShouldntMatchDifferentCasePath() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("filterChainNonLowerCase", FilterChainProxy.class);
-        assertNull(filterChainProxy.getFilters("/some/other/path/blah"));
-    }
+    public void originalChainIsInvokedAfterSecurityChainIfMatchSucceeds() throws Exception {
+        fcp.doFilter(request, response, chain);
 
 
-    @Test
-    public void normalOperationWithNewConfig() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxy", FilterChainProxy.class);
-        checkPathAndFilterOrder(filterChainProxy);
-        doNormalOperation(filterChainProxy);
+        verify(filter).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
+        verify(chain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
     }
     }
 
 
     @Test
     @Test
-    public void normalOperationWithNewConfigRegex() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyRegex", FilterChainProxy.class);
-        checkPathAndFilterOrder(filterChainProxy);
-        doNormalOperation(filterChainProxy);
-    }
+    public void originalFilterChainIsInvokedIfMatchingSecurityChainIsEmpty() throws Exception {
+        LinkedHashMap map = new LinkedHashMap();
+        map.put("/match", Collections.emptyList());
+        fcp.setFilterChainMap(map);
 
 
-    @Test
-    public void normalOperationWithNewConfigNonNamespace() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNonNamespace", FilterChainProxy.class);
-        checkPathAndFilterOrder(filterChainProxy);
-        doNormalOperation(filterChainProxy);
-    }
+        fcp.doFilter(request, response, chain);
 
 
-    @Test
-    public void pathWithNoMatchHasNoFilters() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNoDefaultPath", FilterChainProxy.class);
-        assertEquals(null, filterChainProxy.getFilters("/nomatch"));
+        verify(chain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class));
     }
     }
 
 
     @Test
     @Test
-    public void urlStrippingPropertyIsRespected() throws Exception {
-        FilterChainProxy filterChainProxy = (FilterChainProxy) appCtx.getBean("newFilterChainProxyNoDefaultPath", FilterChainProxy.class);
-
-        // Should only match if we are stripping the query string
-        String url = "/blah.bar?x=something";
-        assertNotNull(filterChainProxy.getFilters(url));
-        assertEquals(2, filterChainProxy.getFilters(url).size());
-        filterChainProxy.setStripQueryStringFromUrls(false);
-        assertNull(filterChainProxy.getFilters(url));
+    public void requestIsWrappedForFilteringWhenMatchIsFound() throws Exception {
+        fcp.doFilter(request, response, chain);
+        verify(filter).doFilter(any(FirewalledRequest.class), any(HttpServletResponse.class), any(FilterChain.class));
+        verify(chain).doFilter(any(FirewalledRequest.class), any(HttpServletResponse.class));
     }
     }
 
 
-    private void checkPathAndFilterOrder(FilterChainProxy filterChainProxy) throws Exception {
-        List filters = filterChainProxy.getFilters("/foo/blah");
-        assertEquals(1, filters.size());
-        assertTrue(filters.get(0) instanceof MockFilter);
-
-        filters = filterChainProxy.getFilters("/some/other/path/blah");
-        assertNotNull(filters);
-        assertEquals(3, filters.size());
-        assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
-        assertTrue(filters.get(1) instanceof MockFilter);
-        assertTrue(filters.get(2) instanceof MockFilter);
-
-        filters = filterChainProxy.getFilters("/do/not/filter");
-        assertEquals(0, filters.size());
-
-        filters = filterChainProxy.getFilters("/another/nonspecificmatch");
-        assertEquals(3, filters.size());
-        assertTrue(filters.get(0) instanceof HttpSessionContextIntegrationFilter);
-        assertTrue(filters.get(1) instanceof AuthenticationProcessingFilter);
-        assertTrue(filters.get(2) instanceof MockFilter);
-    }
-
-    private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
-        MockFilter filter = (MockFilter) appCtx.getBean("mockFilter", MockFilter.class);
-        assertFalse(filter.isWasInitialized());
-        assertFalse(filter.isWasDoFiltered());
-        assertFalse(filter.isWasDestroyed());
-
-        filterChainProxy.init(new MockFilterConfig());
-        assertTrue(filter.isWasInitialized());
-        assertFalse(filter.isWasDoFiltered());
-        assertFalse(filter.isWasDestroyed());
-
-        MockHttpServletRequest request = new MockHttpServletRequest();
-        request.setServletPath("/foo/secure/super/somefile.html");
-
-        MockHttpServletResponse response = new MockHttpServletResponse();
-        MockFilterChain chain = new MockFilterChain(true);
-
-        filterChainProxy.doFilter(request, response, chain);
-        assertTrue(filter.isWasInitialized());
-        assertTrue(filter.isWasDoFiltered());
-        assertFalse(filter.isWasDestroyed());
-
-        request.setServletPath("/a/path/which/doesnt/match/any/filter.html");
-        filterChainProxy.doFilter(request, response, chain);
-
-        filterChainProxy.destroy();
-        assertTrue(filter.isWasInitialized());
-        assertTrue(filter.isWasDoFiltered());
-        assertTrue(filter.isWasDestroyed());
+    @Test
+    public void requestIsWrappedForFilteringWhenMatchIsNotFound() throws Exception {
+        request.setServletPath("/nomatch");
+        fcp.doFilter(request, response, chain);
+        verifyZeroInteractions(filter);
+        verify(chain).doFilter(any(FirewalledRequest.class), any(HttpServletResponse.class));
     }
     }
 
 
-    //~ Inner Classes ==================================================================================================
-
-    private class MockConfigAttribute implements ConfigAttribute {
-        public String getAttribute() {
-            return null;
-        }
-    }
 }
 }

+ 23 - 23
pom.xml

@@ -8,8 +8,8 @@
     <packaging>pom</packaging>
     <packaging>pom</packaging>
 
 
     <modules>
     <modules>
-        <module>core</module>            
-        <module>core-tiger</module>        
+        <module>core</module>
+        <module>core-tiger</module>
         <!-- module>adapters</module -->
         <!-- module>adapters</module -->
         <module>portlet</module>
         <module>portlet</module>
         <module>ntlm</module>
         <module>ntlm</module>
@@ -43,9 +43,9 @@
     <ciManagement>
     <ciManagement>
         <system>bamboo</system>
         <system>bamboo</system>
         <url>https://build.springframework.org/browse/SEC</url>
         <url>https://build.springframework.org/browse/SEC</url>
-    </ciManagement>    
+    </ciManagement>
 
 
-    <distributionManagement>	
+    <distributionManagement>
         <repository>
         <repository>
             <id>spring-release</id>
             <id>spring-release</id>
             <name>Spring Release Repository</name>
             <name>Spring Release Repository</name>
@@ -177,10 +177,10 @@
         </contributor>
         </contributor>
         <contributor>
         <contributor>
             <name>Ruud Senden</name>
             <name>Ruud Senden</name>
-        </contributor>                
+        </contributor>
         <contributor>
         <contributor>
             <name>Michael Mayr</name>
             <name>Michael Mayr</name>
-        </contributor>        
+        </contributor>
     </contributors>
     </contributors>
 
 
     <dependencies>
     <dependencies>
@@ -205,7 +205,7 @@
                 <plugin>
                 <plugin>
                     <groupId>com.springsource.bundlor</groupId>
                     <groupId>com.springsource.bundlor</groupId>
                     <artifactId>com.springsource.bundlor.maven</artifactId>
                     <artifactId>com.springsource.bundlor.maven</artifactId>
-                    <version>1.0.0.M5</version>
+                    <version>1.0.0.M6</version>
                     <executions>
                     <executions>
                         <execution>
                         <execution>
                             <id>bundlor</id>
                             <id>bundlor</id>
@@ -218,7 +218,7 @@
                 </plugin>
                 </plugin>
             </plugins>
             </plugins>
         </pluginManagement>
         </pluginManagement>
-        <plugins>            
+        <plugins>
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-help-plugin</artifactId>
                 <artifactId>maven-help-plugin</artifactId>
@@ -435,11 +435,11 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-report-plugin</artifactId>
                 <artifactId>maven-surefire-report-plugin</artifactId>
                 <version>2.4.2</version>
                 <version>2.4.2</version>
-<!--                
+<!--
                 <configuration>
                 <configuration>
                     <aggregate>true</aggregate>
                     <aggregate>true</aggregate>
-                </configuration>                
--->                
+                </configuration>
+-->
             </plugin>
             </plugin>
             <plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
@@ -449,7 +449,7 @@
                     <excludes>
                     <excludes>
                         <exclude>bigbank/**</exclude>
                         <exclude>bigbank/**</exclude>
                     </excludes>
                     </excludes>
-                </configuration>                
+                </configuration>
             </plugin>
             </plugin>
             <!--
             <!--
                 <plugin>
                 <plugin>
@@ -469,14 +469,14 @@
             </plugin>
             </plugin>
 
 
             <plugin>
             <plugin>
-                <groupId>org.apache.maven.plugins</groupId>                    
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <artifactId>maven-javadoc-plugin</artifactId>
                 <version>2.4</version>
                 <version>2.4</version>
                 <configuration>
                 <configuration>
                     <aggregate>true</aggregate>
                     <aggregate>true</aggregate>
                     <header>Spring Security Framework</header>
                     <header>Spring Security Framework</header>
                     <quiet>true</quiet>
                     <quiet>true</quiet>
-                    <excludePackageNames>sample,bigbank,zzz</excludePackageNames>                                        
+                    <excludePackageNames>sample,bigbank,zzz</excludePackageNames>
                     <links>
                     <links>
                         <link>
                         <link>
                             http://java.sun.com/j2se/1.5.0/docs/api
                             http://java.sun.com/j2se/1.5.0/docs/api
@@ -520,7 +520,7 @@
                       <!--<report>test-javadoc</report> -->
                       <!--<report>test-javadoc</report> -->
                     </reports>
                     </reports>
                   </reportSet>
                   </reportSet>
-                </reportSets>                
+                </reportSets>
             </plugin>
             </plugin>
             <plugin>
             <plugin>
                 <groupId>org.codehaus.mojo</groupId>
                 <groupId>org.codehaus.mojo</groupId>
@@ -530,7 +530,7 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-project-info-reports-plugin</artifactId>
                 <artifactId>maven-project-info-reports-plugin</artifactId>
                 <version>2.0.1</version>
                 <version>2.0.1</version>
-<!--                
+<!--
                 <reportSets>
                 <reportSets>
                     <reportSet>
                     <reportSet>
                         <reports>
                         <reports>
@@ -542,8 +542,8 @@
                         </reports>
                         </reports>
                     </reportSet>
                     </reportSet>
                 </reportSets>
                 </reportSets>
--->                
-            </plugin>           
+-->
+            </plugin>
         </plugins>
         </plugins>
     </reporting>
     </reporting>
 
 
@@ -604,7 +604,7 @@
                 <artifactId>aspectjweaver</artifactId>
                 <artifactId>aspectjweaver</artifactId>
                 <optional>true</optional>
                 <optional>true</optional>
                 <version>1.5.4</version>
                 <version>1.5.4</version>
-            </dependency>	    
+            </dependency>
             <dependency>
             <dependency>
                 <groupId>org.aspectj</groupId>
                 <groupId>org.aspectj</groupId>
                 <artifactId>aspectjrt</artifactId>
                 <artifactId>aspectjrt</artifactId>
@@ -613,15 +613,15 @@
             <dependency>
             <dependency>
                 <groupId>org.springframework</groupId>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-webmvc</artifactId>
                 <artifactId>spring-webmvc</artifactId>
-                <version>${spring.version}</version>                
-            </dependency>            
+                <version>${spring.version}</version>
+            </dependency>
             <dependency>
             <dependency>
                 <groupId>cglib</groupId>
                 <groupId>cglib</groupId>
                 <artifactId>cglib-nodep</artifactId>
                 <artifactId>cglib-nodep</artifactId>
                 <scope>test</scope>
                 <scope>test</scope>
                 <optional>true</optional>
                 <optional>true</optional>
                 <version>2.1_3</version>
                 <version>2.1_3</version>
-            </dependency>	    
+            </dependency>
             <dependency>
             <dependency>
                 <groupId>log4j</groupId>
                 <groupId>log4j</groupId>
                 <artifactId>log4j</artifactId>
                 <artifactId>log4j</artifactId>
@@ -679,7 +679,7 @@
         <jstl.version>1.1.2</jstl.version>
         <jstl.version>1.1.2</jstl.version>
 
 
         <docbook.source>${basedir}/src/docbkx</docbook.source>
         <docbook.source>${basedir}/src/docbkx</docbook.source>
-        <docbook.target>${basedir}/target/site/guide</docbook.target>        
+        <docbook.target>${basedir}/target/site/guide</docbook.target>
     </properties>
     </properties>
 
 
 </project>
 </project>