Browse Source

SEC-2042: AbstractAuthenticationProcessingFilter supports RequestMatcher

Rob Winch 12 years ago
parent
commit
f5a30e55a3
14 changed files with 128 additions and 74 deletions
  1. 1 0
      cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java
  2. 9 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java
  3. 12 13
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java
  4. 11 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java
  5. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy
  6. 6 6
      config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy
  7. 2 2
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy
  8. 4 4
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy
  9. 6 6
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.groovy
  10. 4 4
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpOpenIDLoginTests.groovy
  11. 5 16
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy
  12. 64 17
      web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java
  13. 2 2
      web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageViewFilter.java
  14. 1 2
      web/src/test/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilterTests.java

+ 1 - 0
cas/src/test/java/org/springframework/security/cas/web/CasAuthenticationFilterTests.java

@@ -172,6 +172,7 @@ public class CasAuthenticationFilterTests {
         serviceProperties.setAuthenticateAllArtifacts(true);
         MockHttpServletRequest request = new MockHttpServletRequest();
         request.setParameter("ticket", "ST-1-123");
+        request.setRequestURI("/authenticate");
         MockHttpServletResponse response = new MockHttpServletResponse();
         FilterChain chain = mock(FilterChain.class);
 

+ 9 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/AbstractAuthenticationFilterConfigurer.java

@@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.SimpleUrlAuthenticationFa
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.util.AntPathRequestMatcher;
 import org.springframework.security.web.util.MediaTypeRequestMatcher;
 import org.springframework.security.web.util.RequestMatcher;
 import org.springframework.web.accept.ContentNegotiationStrategy;
@@ -139,10 +140,17 @@ public abstract class AbstractAuthenticationFilterConfigurer<B  extends HttpSecu
      */
     public T loginProcessingUrl(String loginProcessingUrl) {
         this.loginProcessingUrl = loginProcessingUrl;
-        authFilter.setFilterProcessesUrl(loginProcessingUrl);
+        authFilter.setRequiresAuthenticationRequestMatcher(createLoginProcessingUrlMatcher(loginProcessingUrl));
         return getSelf();
     }
 
+    /**
+     * Create the {@link RequestMatcher} given a loginProcessingUrl
+     * @param loginProcessingUrl creates the {@link RequestMatcher} based upon the loginProcessingUrl
+     * @return the {@link RequestMatcher} to use based upon the loginProcessingUrl
+     */
+    protected abstract RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl);
+
     /**
      * Specifies a custom {@link AuthenticationDetailsSource}. The default is {@link WebAuthenticationDetailsSource}.
      *

+ 12 - 13
config/src/main/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurer.java

@@ -15,9 +15,6 @@
  */
 package org.springframework.security.config.annotation.web.configurers;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@@ -26,6 +23,8 @@ import org.springframework.security.web.authentication.RememberMeServices;
 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
 
 /**
  * Adds form based authentication. All attributes have reasonable defaults
@@ -71,7 +70,7 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
      * @see HttpSecurity#formLogin()
      */
     public FormLoginConfigurer() {
-        super(createUsernamePasswordAuthenticationFilter(),"/login");
+        super(new UsernamePasswordAuthenticationFilter(),"/login");
         usernameParameter("username");
         passwordParameter("password");
     }
@@ -193,6 +192,15 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
         initDefaultLoginFilter(http);
     }
 
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#createLoginProcessingUrlMatcher(java.lang.String)
+     */
+    @Override
+    protected RequestMatcher createLoginProcessingUrlMatcher(
+            String loginProcessingUrl) {
+        return new AntPathRequestMatcher(loginProcessingUrl, "POST");
+    }
+
     /**
      * Gets the HTTP parameter that is used to submit the username.
      *
@@ -227,13 +235,4 @@ public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
             loginPageGeneratingFilter.setAuthenticationUrl(getLoginProcessingUrl());
         }
     }
-
-    private static UsernamePasswordAuthenticationFilter createUsernamePasswordAuthenticationFilter() {
-        return new UsernamePasswordAuthenticationFilter() {
-            @Override
-            protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
-                return "POST".equals(request.getMethod()) && super.requiresAuthentication(request, response);
-            }
-        };
-    }
 }

+ 11 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/openid/OpenIDLoginConfigurer.java

@@ -49,6 +49,8 @@ import org.springframework.security.web.authentication.LoginUrlAuthenticationEnt
 import org.springframework.security.web.authentication.RememberMeServices;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
 import org.springframework.security.web.authentication.ui.DefaultLoginPageViewFilter;
+import org.springframework.security.web.util.AntPathRequestMatcher;
+import org.springframework.security.web.util.RequestMatcher;
 
 /**
  * Adds support for OpenID based authentication.
@@ -248,6 +250,15 @@ public final class OpenIDLoginConfigurer<H extends HttpSecurityBuilder<H>> exten
         super.configure(http);
     }
 
+    /* (non-Javadoc)
+     * @see org.springframework.security.config.annotation.web.configurers.AbstractAuthenticationFilterConfigurer#createLoginProcessingUrlMatcher(java.lang.String)
+     */
+    @Override
+    protected RequestMatcher createLoginProcessingUrlMatcher(
+            String loginProcessingUrl) {
+        return new AntPathRequestMatcher(loginProcessingUrl);
+    }
+
     /**
      * Gets the {@link OpenIDConsumer} that was configured or defaults to an {@link OpenID4JavaConsumer}.
      * @return the {@link OpenIDConsumer} to use

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/annotation/BaseWebSpecuritySpec.groovy

@@ -37,7 +37,7 @@ abstract class BaseWebSpecuritySpec extends BaseSpringSpec {
     MockFilterChain chain
 
     def setup() {
-        request = new MockHttpServletRequest()
+        request = new MockHttpServletRequest(method:"GET")
         response = new MockHttpServletResponse()
         chain = new MockFilterChain()
     }

+ 6 - 6
config/src/test/groovy/org/springframework/security/config/annotation/web/SampleWebSecurityConfigurerAdapterTests.groovy

@@ -46,14 +46,14 @@ public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpe
         when: "fail to log in"
             super.setup()
             request.addHeader("Accept", "text/html")
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
             response.getRedirectedUrl() == "/login?error"
         when: "login success"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]
@@ -108,14 +108,14 @@ public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpe
             response.getRedirectedUrl() == "http://localhost/login"
         when: "fail to log in"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
             response.getRedirectedUrl() == "/login?error"
         when: "login success"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]
@@ -197,14 +197,14 @@ public class SampleWebSecurityConfigurerAdapterTests extends BaseWebSpecuritySpe
             response.getRedirectedUrl() == "http://localhost/login"
         when: "fail to log in"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
             response.getRedirectedUrl() == "/login?error"
         when: "login success"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]

+ 2 - 2
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/DefaultLoginPageConfigurerTests.groovy

@@ -77,7 +77,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 </form></body></html>"""
         when: "fail to log in"
             setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
@@ -100,7 +100,7 @@ public class DefaultLoginPageConfigurerTests extends BaseSpringSpec {
 </form></body></html>"""
         when: "login success"
             setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]

+ 4 - 4
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.groovy

@@ -72,8 +72,8 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
             authFilter.passwordParameter == "password"
             authFilter.failureHandler.defaultFailureUrl == "/login?error"
             authFilter.successHandler.defaultTargetUrl == "/"
-            authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "POST"), new MockHttpServletResponse())
-            !authFilter.requiresAuthentication(new MockHttpServletRequest(requestURI : "/login", method: "GET"), new MockHttpServletResponse())
+            authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "POST"), new MockHttpServletResponse())
+            !authFilter.requiresAuthentication(new MockHttpServletRequest(servletPath : "/login", method: "GET"), new MockHttpServletResponse())
 
         and: "SessionFixationProtectionStrategy is configured correctly"
             SessionFixationProtectionStrategy sessionStrategy = ReflectionTestUtils.getField(authFilter,"sessionStrategy")
@@ -82,7 +82,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
         and: "Exception handling is configured correctly"
             AuthenticationEntryPoint authEntryPoint = filterChains[1].filters.find { it instanceof ExceptionTranslationFilter}.authenticationEntryPoint
             MockHttpServletResponse response = new MockHttpServletResponse()
-            authEntryPoint.commence(new MockHttpServletRequest(requestURI: "/private/"), response, new BadCredentialsException(""))
+            authEntryPoint.commence(new MockHttpServletRequest(servletPath: "/private/"), response, new BadCredentialsException(""))
             response.redirectedUrl == "http://localhost/login"
     }
 
@@ -172,7 +172,7 @@ class FormLoginConfigurerTests extends BaseSpringSpec {
             loadConfig(PermitAllIgnoresFailureHandlerConfig)
             FilterChainProxy springSecurityFilterChain = context.getBean(FilterChainProxy)
         when: "access default failureUrl and configured explicit FailureHandler"
-            MockHttpServletRequest request = new MockHttpServletRequest(requestURI:"/login",queryString:"error")
+            MockHttpServletRequest request = new MockHttpServletRequest(servletPath:"/login",requestURI:"/login",queryString:"error",method:"GET")
             MockHttpServletResponse response = new MockHttpServletResponse()
             springSecurityFilterChain.doFilter(request,response,new MockFilterChain())
         then: "access is not granted to the failure handler (sent to login page)"

+ 6 - 6
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.groovy

@@ -50,14 +50,14 @@ public class NamespaceHttpFormLoginTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "http://localhost/login"
         when: "fail to log in"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
             response.getRedirectedUrl() == "/login?error"
         when: "login success"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]
@@ -96,14 +96,14 @@ public class NamespaceHttpFormLoginTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "http://localhost/authentication/login"
         when: "fail to log in"
             super.setup()
-            request.requestURI = "/authentication/login/process"
+            request.servletPath = "/authentication/login/process"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
             then: "sent to login error page"
             response.getRedirectedUrl() == "/authentication/login?failed"
         when: "login success"
             super.setup()
-            request.requestURI = "/authentication/login/process"
+            request.servletPath = "/authentication/login/process"
             request.method = "POST"
             request.parameters.j_username = ["user"] as String[]
             request.parameters.j_password = ["password"] as String[]
@@ -137,14 +137,14 @@ public class NamespaceHttpFormLoginTests extends BaseSpringSpec {
             then: "CustomWebAuthenticationDetailsSource is used"
             findFilter(UsernamePasswordAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
         when: "fail to log in"
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
             response.getRedirectedUrl() == "/custom/failure"
         when: "login success"
             super.setup()
-            request.requestURI = "/login"
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]

+ 4 - 4
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpOpenIDLoginTests.groovy

@@ -67,7 +67,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "http://localhost/login"
         when: "fail to log in"
             setup()
-            request.requestURI = "/login/openid"
+            request.servletPath = "/login/openid"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
@@ -118,7 +118,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "http://localhost/login"
         when: "fail to log in"
             setup()
-            request.requestURI = "/login/openid"
+            request.servletPath = "/login/openid"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
@@ -172,7 +172,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "http://localhost/authentication/login"
         when: "fail to log in"
             setup()
-            request.requestURI = "/authentication/login/process"
+            request.servletPath = "/authentication/login/process"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"
@@ -205,7 +205,7 @@ public class NamespaceHttpOpenIDLoginTests extends BaseSpringSpec {
             findFilter(OpenIDAuthenticationFilter).authenticationDetailsSource.class == CustomWebAuthenticationDetailsSource
             findAuthenticationProvider(OpenIDAuthenticationProvider).userDetailsService == OpenIDLoginCustomRefsConfig.AUDS
         when: "fail to log in"
-            request.requestURI = "/login/openid"
+            request.servletPath = "/login/openid"
             request.method = "POST"
             springSecurityFilterChain.doFilter(request,response,chain)
         then: "sent to login error page"

+ 5 - 16
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy

@@ -54,24 +54,13 @@ import org.springframework.test.util.ReflectionTestUtils;
  *
  */
 public class NamespaceRememberMeTests extends BaseSpringSpec {
-    FilterChainProxy springSecurityFilterChain
-    MockHttpServletRequest request
-    MockHttpServletResponse response
-    MockFilterChain chain
-
-    def setup() {
-        request = new MockHttpServletRequest()
-        response = new MockHttpServletResponse()
-        chain = new MockFilterChain()
-    }
 
     def "http/remember-me"() {
         setup:
             loadConfig(RememberMeConfig)
-            springSecurityFilterChain = context.getBean(FilterChainProxy)
         when: "login with remember me"
-            setup()
-            request.requestURI = "/login"
+            super.setup()
+            request.servletPath = "/login"
             request.method = "POST"
             request.parameters.username = ["user"] as String[]
             request.parameters.password = ["password"] as String[]
@@ -81,7 +70,7 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
         then: "response contains remember me cookie"
             rememberMeCookie != null
         when: "session expires"
-            setup()
+            super.setup()
             request.setCookies(rememberMeCookie)
             request.requestURI = "/abc"
             springSecurityFilterChain.doFilter(request,response,chain)
@@ -90,7 +79,7 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
             SecurityContext context = new HttpSessionSecurityContextRepository().loadContext(new HttpRequestResponseHolder(request, response))
             context.getAuthentication() instanceof RememberMeAuthenticationToken
         when: "logout"
-            setup()
+            super.setup()
             request.setSession(session)
             request.setCookies(rememberMeCookie)
             request.requestURI = "/logout"
@@ -100,7 +89,7 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
             response.getRedirectedUrl() == "/login?logout"
             rememberMeCookie.maxAge == 0
         when: "use remember me after logout"
-            setup()
+            super.setup()
             request.setCookies(rememberMeCookie)
             request.requestURI = "/abc"
             springSecurityFilterChain.doFilter(request,response,chain)

+ 64 - 17
web/src/main/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.java

@@ -40,6 +40,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.WebAttributes;
 import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
 import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
+import org.springframework.security.web.util.RequestMatcher;
 import org.springframework.security.web.util.UrlUtils;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.GenericFilterBean;
@@ -53,8 +54,7 @@ import org.springframework.web.filter.GenericFilterBean;
  * required to process the authentication request tokens created by implementing classes.
  * <p>
  * This filter will intercept a request and attempt to perform authentication from that request if
- * the request URL matches the value of the <tt>filterProcessesUrl</tt> property. This behaviour can modified by
- * overriding the  method {@link #requiresAuthentication(HttpServletRequest, HttpServletResponse) requiresAuthentication}.
+ * the request matches the {@link #setRequiresAuthenticationRequestMatcher(RequestMatcher)}.
  * <p>
  * Authentication is performed by the {@link #attemptAuthentication(HttpServletRequest, HttpServletResponse)
  * attemptAuthentication} method, which must be implemented by subclasses.
@@ -116,10 +116,14 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
     protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
     private RememberMeServices rememberMeServices = new NullRememberMeServices();
 
+    private RequestMatcher requiresAuthenticationRequestMatcher;
+
     /**
      * The URL destination that this filter intercepts and processes (usually
      * something like <code>/j_spring_security_check</code>)
+     * @deprecated use {@link #requiresAuthenticationRequestMatcher} instead
      */
+    @Deprecated
     private String filterProcessesUrl;
 
     private boolean continueChainBeforeSuccessfulAuthentication = false;
@@ -137,15 +141,26 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
      * @param defaultFilterProcessesUrl the default value for <tt>filterProcessesUrl</tt>.
      */
     protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
+        this.requiresAuthenticationRequestMatcher = new FilterProcessUrlRequestMatcher(defaultFilterProcessesUrl);
         this.filterProcessesUrl = defaultFilterProcessesUrl;
     }
 
+    /**
+     * Creates a new instance
+     *
+     * @param requiresAuthenticationRequestMatcher
+     *            the {@link RequestMatcher} used to determine if authentication
+     *            is required. Cannot be null.
+     */
+    protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
+        Assert.notNull(requiresAuthenticationRequestMatcher, "requiresAuthenticationRequestMatcher cannot be null");
+        this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
+    }
+
     //~ Methods ========================================================================================================
 
     @Override
     public void afterPropertiesSet() {
-        Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
-        Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
         Assert.notNull(authenticationManager, "authenticationManager must be specified");
 
         if (rememberMeServices == null) {
@@ -231,21 +246,11 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
      * Subclasses may override for special requirements, such as Tapestry integration.
      *
      * @return <code>true</code> if the filter should attempt authentication, <code>false</code> otherwise.
+     * @deprecated use {@link #setRequiresAuthenticationRequestMatcher(RequestMatcher)} instead
      */
+    @Deprecated
     protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
-        String uri = request.getRequestURI();
-        int pathParamIndex = uri.indexOf(';');
-
-        if (pathParamIndex > 0) {
-            // strip everything after the first semi-colon
-            uri = uri.substring(0, pathParamIndex);
-        }
-
-        if ("".equals(request.getContextPath())) {
-            return uri.endsWith(filterProcessesUrl);
-        }
-
-        return uri.endsWith(request.getContextPath() + filterProcessesUrl);
+        return requiresAuthenticationRequestMatcher.matches(request);
     }
 
     /**
@@ -358,14 +363,29 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
         this.authenticationManager = authenticationManager;
     }
 
+    @Deprecated
     public String getFilterProcessesUrl() {
         return filterProcessesUrl;
     }
 
+    /**
+     * Sets the URL that determines if authentication is required
+     *
+     * @param filterProcessesUrl
+     * @deprecated use {@link #setRequiresAuthenticationRequestMatcher(RequestMatcher)} instead
+     */
+    @Deprecated
     public void setFilterProcessesUrl(String filterProcessesUrl) {
+        this.requiresAuthenticationRequestMatcher = new FilterProcessUrlRequestMatcher(filterProcessesUrl);
         this.filterProcessesUrl = filterProcessesUrl;
     }
 
+    public final void setRequiresAuthenticationRequestMatcher(RequestMatcher requestMatcher) {
+        Assert.notNull(requestMatcher, "requestMatcher cannot be null");
+        this.filterProcessesUrl = null;
+        this.requiresAuthenticationRequestMatcher = requestMatcher;
+    }
+
     public RememberMeServices getRememberMeServices() {
         return rememberMeServices;
     }
@@ -439,4 +459,31 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
     protected AuthenticationFailureHandler getFailureHandler() {
         return failureHandler;
     }
+
+    private static final class FilterProcessUrlRequestMatcher implements RequestMatcher {
+        private final String filterProcessesUrl;
+
+        private FilterProcessUrlRequestMatcher(String filterProcessesUrl) {
+            Assert.hasLength(filterProcessesUrl, "filterProcessesUrl must be specified");
+            Assert.isTrue(UrlUtils.isValidRedirectUrl(filterProcessesUrl), filterProcessesUrl + " isn't a valid redirect URL");
+            this.filterProcessesUrl = filterProcessesUrl;
+        }
+
+        @Override
+        public boolean matches(HttpServletRequest request) {
+            String uri = request.getRequestURI();
+            int pathParamIndex = uri.indexOf(';');
+
+            if (pathParamIndex > 0) {
+                // strip everything after the first semi-colon
+                uri = uri.substring(0, pathParamIndex);
+            }
+
+            if ("".equals(request.getContextPath())) {
+                return uri.endsWith(filterProcessesUrl);
+            }
+
+            return uri.endsWith(request.getContextPath() + filterProcessesUrl);
+        }
+    }
 }

+ 2 - 2
web/src/main/java/org/springframework/security/web/authentication/ui/DefaultLoginPageViewFilter.java

@@ -30,7 +30,7 @@ import org.springframework.security.web.WebAttributes;
 import org.springframework.web.filter.GenericFilterBean;
 
 /**
- * This class generates a default login page if one was not specified. The class is quite similar
+ * This class generates a default login page if one was not specified.
  *
  * @author Rob Winch
  * @since 3.2
@@ -202,7 +202,7 @@ public class DefaultLoginPageViewFilter extends GenericFilterBean {
     }
 
     private boolean matches(HttpServletRequest request, String url) {
-        if(!"GET".equals(request.getMethod())) {
+        if(!"GET".equals(request.getMethod()) || url == null) {
             return false;
         }
         String uri = request.getRequestURI();

+ 1 - 2
web/src/test/java/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilterTests.java

@@ -213,10 +213,9 @@ public class AbstractAuthenticationProcessingFilterTests {
         filter.setAuthenticationFailureHandler(failureHandler);
         filter.setAuthenticationManager(mock(AuthenticationManager.class));
         filter.setAuthenticationSuccessHandler(successHandler);
-        filter.setFilterProcessesUrl(null);
 
         try {
-            filter.afterPropertiesSet();
+            filter.setFilterProcessesUrl(null);
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertEquals("filterProcessesUrl must be specified", expected.getMessage());