Quellcode durchsuchen

Add Cross Site Tracing (XST) & HTTP Method Tampering Protection

Fixes: gh-5377
Rob Winch vor 7 Jahren
Ursprung
Commit
73345e7434
35 geänderte Dateien mit 275 neuen und 86 gelöschten Zeilen
  1. 15 2
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.groovy
  2. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.groovy
  3. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.groovy
  4. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy
  5. 1 1
      config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy
  6. 1 1
      config/src/test/groovy/org/springframework/security/config/http/AbstractHttpConfigTests.groovy
  7. 37 37
      config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy
  8. 1 1
      config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy
  9. 2 2
      config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy
  10. 6 6
      config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy
  11. 1 1
      config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java
  12. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java
  13. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java
  14. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java
  15. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityAntMatchersTests.java
  16. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java
  17. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java
  18. 2 2
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java
  19. 2 2
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java
  20. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java
  21. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java
  22. 1 1
      config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java
  23. 1 1
      config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java
  24. 1 1
      config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java
  25. 1 1
      config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java
  26. 1 1
      config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java
  27. 3 3
      config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java
  28. 1 1
      config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java
  29. 5 0
      config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-AutoConfig.xml
  30. 5 0
      config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-CsrfEnabled.xml
  31. 39 0
      docs/manual/src/docs/asciidoc/_includes/web/security-filter-chain.adoc
  32. 1 1
      web/src/main/java/org/springframework/security/web/FilterChainProxy.java
  33. 72 0
      web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java
  34. 1 1
      web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java
  35. 64 9
      web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java

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

@@ -15,7 +15,10 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
 import org.springframework.security.core.userdetails.PasswordEncodedUser
+import org.springframework.security.web.firewall.StrictHttpFirewall
 
 import javax.servlet.http.HttpServletResponse
 
@@ -44,7 +47,7 @@ class CsrfConfigurerTests extends BaseSpringSpec {
 	@Unroll
 	def "csrf applied by default"() {
 		setup:
-		loadConfig(CsrfAppliedDefaultConfig)
+		loadConfig(CsrfAppliedDefaultConfig, AllowHttpMethodsFirewallConfig)
 		request.method = httpMethod
 		clearCsrfToken()
 		when:
@@ -66,11 +69,21 @@ class CsrfConfigurerTests extends BaseSpringSpec {
 
 	def "csrf default creates CsrfRequestDataValueProcessor"() {
 		when:
-		loadConfig(CsrfAppliedDefaultConfig)
+		loadConfig(CsrfAppliedDefaultConfig, AllowHttpMethodsFirewallConfig)
 		then:
 		context.getBean(RequestDataValueProcessor)
 	}
 
+	@Configuration
+	static class AllowHttpMethodsFirewallConfig {
+		@Bean
+		StrictHttpFirewall strictHttpFirewall() {
+			StrictHttpFirewall result = new StrictHttpFirewall();
+			result.setAllowedHttpMethods(StrictHttpFirewall.ALLOW_ANY_HTTP_METHOD);
+			return result;
+		}
+	}
+
 	@EnableWebSecurity
 	static class CsrfAppliedDefaultConfig extends WebSecurityConfigurerAdapter {
 

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.groovy

@@ -46,7 +46,7 @@ public class NamespaceHttpFirewallTests extends BaseSpringSpec {
 	MockFilterChain chain
 
 	def setup() {
-		request = new MockHttpServletRequest()
+		request = new MockHttpServletRequest("GET", "")
 		response = new MockHttpServletResponse()
 		chain = new MockFilterChain()
 	}

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.groovy

@@ -44,7 +44,7 @@ public class NamespaceHttpPortMappingsTests extends BaseSpringSpec {
 	MockFilterChain chain
 
 	def setup() {
-		request = new MockHttpServletRequest()
+		request = new MockHttpServletRequest("GET", "")
 		request.setMethod("GET")
 		response = new MockHttpServletResponse()
 		chain = new MockFilterChain()

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

@@ -371,7 +371,7 @@ public class NamespaceRememberMeTests extends BaseSpringSpec {
 	}
 
 	Cookie createRememberMeCookie() {
-		MockHttpServletRequest request = new MockHttpServletRequest()
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 		MockHttpServletResponse response = new MockHttpServletResponse()
 		super.setupCsrf("CSRF_TOKEN", request, response)
 

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy

@@ -270,7 +270,7 @@ public class RememberMeConfigurerTests extends BaseSpringSpec {
 	}
 
 	Cookie createRememberMeCookie() {
-		MockHttpServletRequest request = new MockHttpServletRequest()
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 		MockHttpServletResponse response = new MockHttpServletResponse()
 		super.setupCsrf("CSRF_TOKEN", request, response)
 

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/http/AbstractHttpConfigTests.groovy

@@ -67,7 +67,7 @@ abstract class AbstractHttpConfigTests extends AbstractXmlConfigTests {
 	}
 
 	FilterInvocation createFilterinvocation(String path, String method) {
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setMethod(method);
 		request.setRequestURI(null);
 		request.setServletPath(path);

+ 37 - 37
config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy

@@ -69,7 +69,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, defaultHeaders)
 	}
@@ -83,7 +83,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, defaultHeaders)
 	}
@@ -98,7 +98,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		def expectedHeaders = [:] << defaultHeaders
 		expectedHeaders['X-Frame-Options'] = 'SAMEORIGIN'
 
@@ -131,7 +131,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		expect:
 		assertHeaders(response, ['X-Content-Type-Options':'nosniff'])
@@ -147,7 +147,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		expect:
 		assertHeaders(response, ['X-Frame-Options':'DENY'])
@@ -163,7 +163,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		expect:
 		assertHeaders(response, ['X-Frame-Options':'DENY'])
@@ -179,7 +179,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		expect:
 		assertHeaders(response, ['X-Frame-Options':'SAMEORIGIN'])
@@ -228,7 +228,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response, ['X-Frame-Options':'ALLOW-FROM https://example.com'])
@@ -246,7 +246,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
 
-		def request = new MockHttpServletRequest()
+		def request = new MockHttpServletRequest("GET", "")
 		request.setParameter("from", "https://example.com");
 		hf.doFilter(request, response, new MockFilterChain())
 
@@ -265,7 +265,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response, ['a':'b'])
@@ -283,7 +283,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response , ['a':'b', 'c':'d'])
@@ -304,7 +304,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['abc':'def'])
 	}
@@ -346,7 +346,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response, ['X-XSS-Protection':'1; mode=block'])
@@ -363,7 +363,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response, ['X-XSS-Protection':'1; mode=block'])
@@ -380,7 +380,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 
 		def hf = getFilter(HeaderWriterFilter)
 		MockHttpServletResponse response = new MockHttpServletResponse()
-		hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+		hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 
 		then:
 		assertHeaders(response, ['X-XSS-Protection':'0'])
@@ -413,7 +413,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 			MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
 									'Expires' : '0',
@@ -431,7 +431,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 			MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Strict-Transport-Security': 'max-age=31536000 ; includeSubDomains'])
 	}
@@ -447,7 +447,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 			MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			response.headerNames.empty
 	}
@@ -465,7 +465,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 			MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Strict-Transport-Security': 'max-age=1'])
 	}
@@ -515,7 +515,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 			MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
 	}
@@ -535,7 +535,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 				when:
-						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 				then:
 						assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
 		}
@@ -555,7 +555,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 		when:
-				springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+				springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 				response.headerNames.empty
 	}
@@ -575,7 +575,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 				when:
-						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 				then:
 						assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=604800 ; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="'])
 		}
@@ -595,7 +595,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 				when:
-						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
+						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
 				then:
 						assertHeaders(response, ['Public-Key-Pins': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="'])
 		}
@@ -615,7 +615,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 				when:
-						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
+						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
 				then:
 						assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; includeSubDomains'])
 		}
@@ -635,7 +635,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						def springSecurityFilterChain = appContext.getBean(FilterChainProxy)
 						MockHttpServletResponse response = new MockHttpServletResponse()
 				when:
-						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true), response, new MockFilterChain())
+						springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure: true, method: "GET"), response, new MockFilterChain())
 				then:
 						assertHeaders(response, ['Public-Key-Pins-Report-Only': 'max-age=5184000 ; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" ; report-uri="http://example.net/pkp-report"'])
 		}
@@ -657,7 +657,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			expectedHeaders.remove('Expires')
 			expectedHeaders.remove('Pragma')
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, expectedHeaders)
 	}
@@ -675,7 +675,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders.remove('X-Content-Type-Options')
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, expectedHeaders)
 	}
@@ -693,7 +693,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders.remove('Strict-Transport-Security')
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, expectedHeaders)
 	}
@@ -714,7 +714,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 						MockHttpServletResponse response = new MockHttpServletResponse()
 						def expectedHeaders = [:] << defaultHeaders
 		when:
-				springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+				springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 				assertHeaders(response, expectedHeaders)
 	}
@@ -732,7 +732,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders.remove('X-Frame-Options')
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, expectedHeaders)
 	}
@@ -750,7 +750,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders.remove('X-XSS-Protection')
 		when:
-			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			springSecurityFilterChain.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, expectedHeaders)
 	}
@@ -853,7 +853,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders['Content-Security-Policy'] = 'default-src \'self\''
 		then:
@@ -885,7 +885,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Content-Security-Policy':'default-src \'self\''])
 	}
@@ -913,7 +913,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest(secure:true, method: "GET"), response, new MockFilterChain())
 			def expectedHeaders = [:] << defaultHeaders
 			expectedHeaders['Content-Security-Policy-Report-Only'] = 'default-src https:; report-uri https://example.com/'
 		then:
@@ -931,7 +931,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Referrer-Policy': 'no-referrer'])
 	}
@@ -947,7 +947,7 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 		when:
 			def hf = getFilter(HeaderWriterFilter)
 			MockHttpServletResponse response = new MockHttpServletResponse()
-			hf.doFilter(new MockHttpServletRequest(), response, new MockFilterChain())
+			hf.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain())
 		then:
 			assertHeaders(response, ['Referrer-Policy': 'same-origin'])
 	}

+ 1 - 1
config/src/test/groovy/org/springframework/security/config/http/MiscHttpConfigTests.groovy

@@ -142,7 +142,7 @@ class MiscHttpConfigTests extends AbstractHttpConfigTests {
 		createAppContext()
 		then:
 		Filter debugFilter = appContext.getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN);
-		MockHttpServletRequest request = new MockHttpServletRequest()
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 		request.setServletPath("/unprotected");
 		debugFilter.doFilter(request, new MockHttpServletResponse(), new MockFilterChain());
 		request.setServletPath("/nomatch");

+ 2 - 2
config/src/test/groovy/org/springframework/security/config/http/MultiHttpBlockConfigTests.groovy

@@ -93,7 +93,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
 			UserDetailsService uds = appContext.getBean('uds')
 			UserDetailsService uds2 = appContext.getBean('uds2')
 		when:
-			MockHttpServletRequest request = new MockHttpServletRequest()
+			MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 			MockHttpServletResponse response = new MockHttpServletResponse()
 			MockFilterChain chain = new MockFilterChain()
 			request.servletPath = "/first/login"
@@ -104,7 +104,7 @@ class MultiHttpBlockConfigTests extends AbstractHttpConfigTests {
 			verify(uds).loadUserByUsername(anyString()) || true
 			verifyZeroInteractions(uds2) || true
 		when:
-			MockHttpServletRequest request2 = new MockHttpServletRequest()
+			MockHttpServletRequest request2 = new MockHttpServletRequest("GET", "")
 			MockHttpServletResponse response2 = new MockHttpServletResponse()
 			MockFilterChain chain2 = new MockFilterChain()
 			request2.servletPath = "/login"

+ 6 - 6
config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy

@@ -115,7 +115,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
 			createAppContext()
 			SessionRegistry registry = appContext.getBean(SessionRegistry)
 			registry.registerNewSession("1", new User("user","password",AuthorityUtils.createAuthorityList("ROLE_USER")))
-			MockHttpServletRequest request = new MockHttpServletRequest()
+			MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 			MockHttpServletResponse response = new MockHttpServletResponse()
 			String credentials = "user:password"
 			request.addHeader("Authorization", "Basic " + credentials.bytes.encodeBase64())
@@ -134,7 +134,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
 				}
 			}
 			createAppContext()
-			MockHttpServletRequest request = new MockHttpServletRequest()
+			MockHttpServletRequest request = new MockHttpServletRequest("GET", "")
 			MockHttpServletResponse response = new MockHttpServletResponse()
 			String originalSessionId = request.session.id
 			String credentials = "user:password"
@@ -282,7 +282,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
 			mockBean(SessionAuthenticationStrategy,'ss')
 			createAppContext()
 
-			MockHttpServletRequest request = new MockHttpServletRequest();
+			MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 			request.getSession();
 			request.servletPath = "/login"
 			request.setMethod("POST");
@@ -343,15 +343,15 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
 			}
 		};
 		when: "First session is established"
-		seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
+		seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
 		then: "ok"
 		mockResponse.redirectedUrl == null
 		when: "Second session is established"
-		seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
+		seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
 		then: "ok"
 		mockResponse.redirectedUrl == null
 		when: "Third session is established"
-		seshFilter.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
+		seshFilter.doFilter(new MockHttpServletRequest("GET", ""), response, new MockFilterChain());
 		then: "Rejected"
 		mockResponse.redirectedUrl == "/max-exceeded";
 	}

+ 1 - 1
config/src/test/java/org/springframework/security/config/FilterChainProxyConfigTests.java

@@ -152,7 +152,7 @@ public class FilterChainProxyConfigTests {
 	}
 
 	private void doNormalOperation(FilterChainProxy filterChainProxy) throws Exception {
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setServletPath("/foo/secure/super/somefile.html");
 
 		MockHttpServletResponse response = new MockHttpServletResponse();

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/builders/WebSecurityTests.java

@@ -54,7 +54,7 @@ public class WebSecurityTests {
 
 	@Before
 	public void setup() {
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.request.setMethod("GET");
 		this.response = new MockHttpServletResponse();
 		this.chain = new MockFilterChain();

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

@@ -274,7 +274,7 @@ public class WebSecurityConfigurationTests {
 	public void securityExpressionHandlerWhenPermissionEvaluatorBeanThenPermissionEvaluatorUsed() throws Exception {
 		this.spring.register(WebSecurityExpressionHandlerPermissionEvaluatorBeanConfig.class).autowire();
 		TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "notused");
-		FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest(), new MockHttpServletResponse(), new MockFilterChain());
+		FilterInvocation invocation = new FilterInvocation(new MockHttpServletRequest("GET", ""), new MockHttpServletResponse(), new MockFilterChain());
 
 		AbstractSecurityExpressionHandler handler = this.spring.getContext().getBean(AbstractSecurityExpressionHandler.class);
 		EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, invocation);

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeRequestsTests.java

@@ -68,7 +68,7 @@ public class AuthorizeRequestsTests {
 	@Before
 	public void setup() {
 		this.servletContext = spy(new MockServletContext());
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.request.setMethod("GET");
 		this.response = new MockHttpServletResponse();
 		this.chain = new MockFilterChain();

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityAntMatchersTests.java

@@ -51,7 +51,7 @@ public class HttpSecurityAntMatchersTests {
 
 	@Before
 	public void setup() {
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();
 	}

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java

@@ -52,7 +52,7 @@ public class HttpSecurityLogoutTests {
 
 	@Before
 	public void setup() {
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();
 	}

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java

@@ -55,7 +55,7 @@ public class HttpSecurityRequestMatchersTests {
 
 	@Before
 	public void setup() {
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.request.setMethod("GET");
 		this.response = new MockHttpServletResponse();
 		this.chain = new MockFilterChain();

+ 2 - 2
config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java

@@ -72,7 +72,7 @@ public class SessionManagementConfigurerServlet31Tests {
 
 	@Before
 	public void setup() {
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();
 	}
@@ -88,7 +88,7 @@ public class SessionManagementConfigurerServlet31Tests {
 	public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
 		spy(ReflectionUtils.class);
 		Method method = mock(Method.class);
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.getSession();
 		request.setServletPath("/login");
 		request.setMethod("POST");

+ 2 - 2
config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationConfigurerTests.java

@@ -55,7 +55,7 @@ public class UrlAuthorizationConfigurerTests {
 
 	@Before
 	public void setup() {
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.request.setMethod("GET");
 		this.response = new MockHttpServletResponse();
 		this.chain = new MockFilterChain();
@@ -211,4 +211,4 @@ public class UrlAuthorizationConfigurerTests {
 
 		this.context.getAutowireCapableBeanFactory().autowireBean(this);
 	}
-}
+}

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java

@@ -140,7 +140,7 @@ public class OAuth2ClientConfigurerTests {
 
 		AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
 				new HttpSessionOAuth2AuthorizationRequestRepository();
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		MockHttpServletResponse response = new MockHttpServletResponse();
 		authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
 

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java

@@ -104,7 +104,7 @@ public class OAuth2LoginConfigurerTests {
 
 	@Before
 	public void setup() {
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.response = new MockHttpServletResponse();
 		this.filterChain = new MockFilterChain();
 

+ 1 - 1
config/src/test/java/org/springframework/security/config/annotation/web/socket/AbstractSecurityWebSocketMessageBrokerConfigurerTests.java

@@ -493,7 +493,7 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
 	}
 
 	private MockHttpServletRequest sockjsHttpRequest(String mapping) {
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setMethod("GET");
 		request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
 				"/289/tpyx6mde/websocket");

+ 1 - 1
config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java

@@ -65,7 +65,7 @@ public class GrantedAuthorityDefaultsJcTests {
 	public void setup() {
 		setup("USER");
 
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		request.setMethod("GET");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();

+ 1 - 1
config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java

@@ -58,7 +58,7 @@ public class GrantedAuthorityDefaultsXmlTests {
 	public void setup() {
 		setup("USER");
 
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		request.setMethod("GET");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();

+ 1 - 1
config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java

@@ -123,7 +123,7 @@ public class FilterSecurityMetadataSourceBeanDefinitionParserTests {
 	}
 
 	private FilterInvocation createFilterInvocation(String path, String method) {
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setRequestURI(null);
 		request.setMethod(method);
 

+ 1 - 1
config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java

@@ -52,7 +52,7 @@ public class NamespaceHttpBasicTests {
 
 	@Before
 	public void setup() {
-		this.request = new MockHttpServletRequest();
+		this.request = new MockHttpServletRequest("GET", "");
 		this.request.setMethod("GET");
 		this.response = new MockHttpServletResponse();
 		this.chain = new MockFilterChain();

+ 3 - 3
config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java

@@ -73,7 +73,7 @@ public class SessionManagementConfigServlet31Tests {
 
 	@Before
 	public void setup() {
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();
 	}
@@ -89,7 +89,7 @@ public class SessionManagementConfigServlet31Tests {
 	public void changeSessionIdDefaultsInServlet31Plus() throws Exception {
 		spy(ReflectionUtils.class);
 		Method method = mock(Method.class);
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.getSession();
 		request.setServletPath("/login");
 		request.setMethod("POST");
@@ -112,7 +112,7 @@ public class SessionManagementConfigServlet31Tests {
 	public void changeSessionId() throws Exception {
 		spy(ReflectionUtils.class);
 		Method method = mock(Method.class);
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.getSession();
 		request.setServletPath("/login");
 		request.setMethod("POST");

+ 1 - 1
config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java

@@ -55,7 +55,7 @@ public class CustomHttpSecurityConfigurerTests {
 
 	@Before
 	public void setup() {
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		response = new MockHttpServletResponse();
 		chain = new MockFilterChain();
 		request.setMethod("GET");

+ 5 - 0
config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-AutoConfig.xml

@@ -18,10 +18,15 @@
 <b:beans xmlns:b="http://www.springframework.org/schema/beans"
 		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 		 xmlns="http://www.springframework.org/schema/security"
+		 xmlns:p="http://www.springframework.org/schema/p"
 		 xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
 		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
+	<http-firewall ref="firewall"/>
 	<http auto-config="true"/>
 
 	<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
+
+	<b:bean id="firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall"
+			p:unsafeAllowAnyHttpMethod="true"/>
 </b:beans>

+ 5 - 0
config/src/test/resources/org/springframework/security/config/http/CsrfConfigTests-CsrfEnabled.xml

@@ -18,13 +18,18 @@
 <b:beans xmlns:b="http://www.springframework.org/schema/beans"
 		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 		 xmlns="http://www.springframework.org/schema/security"
+		 xmlns:p="http://www.springframework.org/schema/p"
 		 xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
 		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
+	<http-firewall ref="firewall"/>
 	<http auto-config="true">
 		<intercept-url pattern="/authenticated/**" access="authenticated"/>
 		<csrf/>
 	</http>
 
 	<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
+
+	<b:bean id="firewall" class="org.springframework.security.web.firewall.StrictHttpFirewall"
+			p:unsafeAllowAnyHttpMethod="true"/>
 </b:beans>

+ 39 - 0
docs/manual/src/docs/asciidoc/_includes/web/security-filter-chain.adoc

@@ -179,6 +179,45 @@ public StrictHttpFirewall httpFirewall() {
 }
 ----
 
+The `StrictHttpFirewall` provides a whitelist of valid HTTP methods that are allowed to protect against https://www.owasp.org/index.php/Cross_Site_Tracing[Cross Site Tracing (XST)] and https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)[HTTP Verb Tampering].
+The default valid methods are "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", and "PUT".
+If your application needs to modify the valid methods, you can configure a custom `StrictHttpFirewall` bean.
+For example, the following will only allow HTTP "GET" and "POST" methods:
+
+
+[source,xml]
+----
+<b:bean id="httpFirewall"
+      class="org.springframework.security.web.firewall.StrictHttpFirewall"
+      p:allowedHttpMethods="GET,HEAD"/>
+
+<http-firewall ref="httpFirewall"/>
+----
+
+The same thing can be achieved with Java Configuration by exposing a `StrictHttpFirewall` bean.
+
+[source,java]
+----
+@Bean
+public StrictHttpFirewall httpFirewall() {
+    StrictHttpFirewall firewall = new StrictHttpFirewall();
+    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
+    return firewall;
+}
+----
+
+[TIP]
+====
+If you are using `new MockHttpServletRequest()` it currently creates an HTTP method as an empty String "".
+This is an invalid HTTP method and will be rejected by Spring Security.
+You can resolve this by replacing it with `new MockHttpServletRequest("GET", "")`.
+See https://jira.spring.io/browse/SPR-16851[SPR_16851] for an issue requesting to improve this.
+====
+
+If you must allow any HTTP method (not recommended), you can use `StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`.
+This will disable validation of the HTTP method entirely.
+
+
 === Use with other Filter-Based Frameworks
 If you're using some other framework that is also filter-based, then you need to make sure that the Spring Security filters come first.
 This enables the `SecurityContextHolder` to be populated in time for use by the other filters.

+ 1 - 1
web/src/main/java/org/springframework/security/web/FilterChainProxy.java

@@ -238,7 +238,7 @@ public class FilterChainProxy extends GenericFilterBean {
 	 * @return matching filter list
 	 */
 	public List<Filter> getFilters(String url) {
-		return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, null)
+		return getFilters(firewall.getFirewalledRequest((new FilterInvocation(url, "GET")
 				.getRequest())));
 	}
 

+ 72 - 0
web/src/main/java/org/springframework/security/web/firewall/StrictHttpFirewall.java

@@ -16,6 +16,8 @@
 
 package org.springframework.security.web.firewall;
 
+import org.springframework.http.HttpMethod;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.util.Arrays;
@@ -35,6 +37,11 @@ import java.util.Set;
  * </p>
  * <ul>
  * <li>
+ * Rejects HTTP methods that are not allowed. This specified to block
+ * <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">HTTP Verb tampering and XST attacks</a>.
+ * See {@link #setAllowedHttpMethods(Collection)}
+ * </li>
+ * <li>
  * Rejects URLs that are not normalized to avoid bypassing security constraints. There is
  * no way to disable this as it is considered extremely risky to disable this constraint.
  * A few options to allow this behavior is to normalize the request prior to the firewall
@@ -66,6 +73,11 @@ import java.util.Set;
  * @since 4.2.4
  */
 public class StrictHttpFirewall implements HttpFirewall {
+	/**
+	 * Used to specify to {@link #setAllowedHttpMethods(Collection)} that any HTTP method should be allowed.
+	 */
+	private static final Set<String> ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(Collections.emptySet());
+
 	private static final String ENCODED_PERCENT = "%25";
 
 	private static final String PERCENT = "%";
@@ -82,6 +94,8 @@ public class StrictHttpFirewall implements HttpFirewall {
 
 	private Set<String> decodedUrlBlacklist = new HashSet<String>();
 
+	private Set<String> allowedHttpMethods = createDefaultAllowedHttpMethods();
+
 	public StrictHttpFirewall() {
 		urlBlacklistsAddAll(FORBIDDEN_SEMICOLON);
 		urlBlacklistsAddAll(FORBIDDEN_FORWARDSLASH);
@@ -92,6 +106,39 @@ public class StrictHttpFirewall implements HttpFirewall {
 		this.decodedUrlBlacklist.add(PERCENT);
 	}
 
+	/**
+	 * Sets if any HTTP method is allowed. If this set to true, then no validation on the HTTP method will be performed.
+	 * This can open the application up to <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">
+	 * HTTP Verb tampering and XST attacks</a>
+	 * @param unsafeAllowAnyHttpMethod if true, disables HTTP method validation, else resets back to the defaults. Default is false.
+	 * @see #setAllowedHttpMethods(Collection)
+	 * @since 5.1
+	 */
+	public void setUnsafeAllowAnyHttpMethod(boolean unsafeAllowAnyHttpMethod) {
+		this.allowedHttpMethods = unsafeAllowAnyHttpMethod ? ALLOW_ANY_HTTP_METHOD : createDefaultAllowedHttpMethods();
+	}
+
+	/**
+	 * <p>
+	 * Determines which HTTP methods should be allowed. The default is to allow "DELETE", "GET", "HEAD", "OPTIONS",
+	 * "PATCH", "POST", and "PUT".
+	 * </p>
+	 *
+	 * @param allowedHttpMethods the case-sensitive collection of HTTP methods that are allowed.
+	 * @see #setUnsafeAllowAnyHttpMethod(boolean)
+	 * @since 5.1
+	 */
+	public void setAllowedHttpMethods(Collection<String> allowedHttpMethods) {
+		if (allowedHttpMethods == null) {
+			throw new IllegalArgumentException("allowedHttpMethods cannot be null");
+		}
+		if (allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
+			this.allowedHttpMethods = ALLOW_ANY_HTTP_METHOD;
+		} else {
+			this.allowedHttpMethods = new HashSet<>(allowedHttpMethods);
+		}
+	}
+
 	/**
 	 * <p>
 	 * Determines if semicolon is allowed in the URL (i.e. matrix variables). The default
@@ -242,6 +289,7 @@ public class StrictHttpFirewall implements HttpFirewall {
 
 	@Override
 	public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException {
+		rejectForbiddenHttpMethod(request);
 		rejectedBlacklistedUrls(request);
 
 		if (!isNormalized(request)) {
@@ -259,6 +307,18 @@ public class StrictHttpFirewall implements HttpFirewall {
 		};
 	}
 
+	private void rejectForbiddenHttpMethod(HttpServletRequest request) {
+		if (this.allowedHttpMethods == ALLOW_ANY_HTTP_METHOD) {
+			return;
+		}
+		if (!this.allowedHttpMethods.contains(request.getMethod())) {
+			throw new RequestRejectedException("The request was rejected because the HTTP method \"" +
+					request.getMethod() +
+					"\" was not included within the whitelist " +
+					this.allowedHttpMethods);
+		}
+	}
+
 	private void rejectedBlacklistedUrls(HttpServletRequest request) {
 		for (String forbidden : this.encodedUrlBlacklist) {
 			if (encodedUrlContains(request, forbidden)) {
@@ -277,6 +337,18 @@ public class StrictHttpFirewall implements HttpFirewall {
 		return new FirewalledResponse(response);
 	}
 
+	private static Set<String> createDefaultAllowedHttpMethods() {
+		Set<String> result = new HashSet<>();
+		result.add(HttpMethod.DELETE.name());
+		result.add(HttpMethod.GET.name());
+		result.add(HttpMethod.HEAD.name());
+		result.add(HttpMethod.OPTIONS.name());
+		result.add(HttpMethod.PATCH.name());
+		result.add(HttpMethod.POST.name());
+		result.add(HttpMethod.PUT.name());
+		return result;
+	}
+
 	private static boolean isNormalized(HttpServletRequest request) {
 		if (!isNormalized(request.getRequestURI())) {
 			return false;

+ 1 - 1
web/src/test/java/org/springframework/security/web/FilterChainProxyTests.java

@@ -69,7 +69,7 @@ public class FilterChainProxyTests {
 		fcp = new FilterChainProxy(new DefaultSecurityFilterChain(matcher,
 				Arrays.asList(filter)));
 		fcp.setFilterChainValidator(mock(FilterChainProxy.FilterChainValidator.class));
-		request = new MockHttpServletRequest();
+		request = new MockHttpServletRequest("GET", "");
 		request.setServletPath("/path");
 		response = new MockHttpServletResponse();
 		chain = mock(FilterChain.class);

+ 64 - 9
web/src/test/java/org/springframework/security/web/firewall/StrictHttpFirewallTests.java

@@ -16,11 +16,17 @@
 
 package org.springframework.security.web.firewall;
 
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.fail;
+
+import java.util.Arrays;
+import java.util.List;
+
 import org.junit.Test;
+import org.springframework.http.HttpMethod;
 import org.springframework.mock.web.MockHttpServletRequest;
 
-import static org.assertj.core.api.Assertions.fail;
-
 /**
  * @author Rob Winch
  */
@@ -31,12 +37,61 @@ public class StrictHttpFirewallTests {
 
 	private StrictHttpFirewall firewall = new StrictHttpFirewall();
 
-	private MockHttpServletRequest request = new MockHttpServletRequest();
+	private MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+
+	@Test
+	public void getFirewalledRequestWhenInvalidMethodThenThrowsRequestRejectedException() {
+		this.request.setMethod("INVALID");
+		assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
+				.isInstanceOf(RequestRejectedException.class);
+	}
+
+	// blocks XST attacks
+	@Test
+	public void getFirewalledRequestWhenTraceMethodThenThrowsRequestRejectedException() {
+		this.request.setMethod(HttpMethod.TRACE.name());
+		assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
+				.isInstanceOf(RequestRejectedException.class);
+	}
+
+	@Test
+	// blocks XST attack if request is forwarded to a Microsoft IIS web server
+	public void getFirewalledRequestWhenTrackMethodThenThrowsRequestRejectedException() {
+		this.request.setMethod("TRACK");
+		assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
+				.isInstanceOf(RequestRejectedException.class);
+	}
+
+	@Test
+	// HTTP methods are case sensitive
+	public void getFirewalledRequestWhenLowercaseGetThenThrowsRequestRejectedException() {
+		this.request.setMethod("get");
+		assertThatThrownBy(() -> this.firewall.getFirewalledRequest(this.request))
+				.isInstanceOf(RequestRejectedException.class);
+	}
+
+	@Test
+	public void getFirewalledRequestWhenAllowedThenNoException() {
+		List<String> allowedMethods = Arrays.asList("DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT");
+		for (String allowedMethod : allowedMethods) {
+			this.request = new MockHttpServletRequest(allowedMethod, "");
+			assertThatCode(() -> this.firewall.getFirewalledRequest(this.request))
+					.doesNotThrowAnyException();
+		}
+	}
+
+	@Test
+	public void getFirewalledRequestWhenInvalidMethodAndAnyMethodThenNoException() {
+		this.firewall.setUnsafeAllowAnyHttpMethod(true);
+		this.request.setMethod("INVALID");
+		assertThatCode(() -> this.firewall.getFirewalledRequest(this.request))
+				.doesNotThrowAnyException();
+	}
 
 	@Test
 	public void getFirewalledRequestWhenRequestURINotNormalizedThenThrowsRequestRejectedException() throws Exception {
 		for (String path : this.unnormalizedPaths) {
-			this.request = new MockHttpServletRequest();
+			this.request = new MockHttpServletRequest("GET", "");
 			this.request.setRequestURI(path);
 			try {
 				this.firewall.getFirewalledRequest(this.request);
@@ -49,7 +104,7 @@ public class StrictHttpFirewallTests {
 	@Test
 	public void getFirewalledRequestWhenContextPathNotNormalizedThenThrowsRequestRejectedException() throws Exception {
 		for (String path : this.unnormalizedPaths) {
-			this.request = new MockHttpServletRequest();
+			this.request = new MockHttpServletRequest("GET", "");
 			this.request.setContextPath(path);
 			try {
 				this.firewall.getFirewalledRequest(this.request);
@@ -62,7 +117,7 @@ public class StrictHttpFirewallTests {
 	@Test
 	public void getFirewalledRequestWhenServletPathNotNormalizedThenThrowsRequestRejectedException() throws Exception {
 		for (String path : this.unnormalizedPaths) {
-			this.request = new MockHttpServletRequest();
+			this.request = new MockHttpServletRequest("GET", "");
 			this.request.setServletPath(path);
 			try {
 				this.firewall.getFirewalledRequest(this.request);
@@ -75,7 +130,7 @@ public class StrictHttpFirewallTests {
 	@Test
 	public void getFirewalledRequestWhenPathInfoNotNormalizedThenThrowsRequestRejectedException() throws Exception {
 		for (String path : this.unnormalizedPaths) {
-			this.request = new MockHttpServletRequest();
+			this.request = new MockHttpServletRequest("GET", "");
 			this.request.setPathInfo(path);
 			try {
 				this.firewall.getFirewalledRequest(this.request);
@@ -352,7 +407,7 @@ public class StrictHttpFirewallTests {
 	public void getFirewalledRequestWhenAllowUrlEncodedSlashAndLowercaseEncodedPathThenNoException() {
 		this.firewall.setAllowUrlEncodedSlash(true);
 		this.firewall.setAllowSemicolon(true);
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setRequestURI("/context-root/a/b;%2f1/c");
 		request.setContextPath("/context-root");
 		request.setServletPath("");
@@ -365,7 +420,7 @@ public class StrictHttpFirewallTests {
 	public void getFirewalledRequestWhenAllowUrlEncodedSlashAndUppercaseEncodedPathThenNoException() {
 		this.firewall.setAllowUrlEncodedSlash(true);
 		this.firewall.setAllowSemicolon(true);
-		MockHttpServletRequest request = new MockHttpServletRequest();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
 		request.setRequestURI("/context-root/a/b;%2F1/c");
 		request.setContextPath("/context-root");
 		request.setServletPath("");