# HttpFirewall

在针对你定义的模式进行测试时，了解该机制是什么以及使用了什么URL值是很重要的。

Servlet 规范为`HttpServletRequest`定义了几个属性，这些属性可以通过getter方法访问，并且我们可能希望对其进行匹配。它们是`contextPath`，`servletPath`，`pathInfo`和`queryString`。 Spring 安全性只对保护应用程序内的路径感兴趣，因此`contextPath`被忽略。不幸的是， Servlet 规范并没有确切地定义`servletPath`和`pathInfo`对于特定的请求URI的值将包含什么。例如，一个URL的每个路径段可以包含参数，如[RFC 2396](https://www.ietf.org/rfc/rfc2396.txt)<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>。规范没有明确说明是否应该将这些值包含在`servletPath`和`pathInfo`值中，并且不同的容器之间的行为是不同的。当应用程序部署在不从这些值中删除路径参数的容器中时，存在这样的危险：攻击者可能会将它们添加到所请求的URL中，从而导致模式匹配意外成功或失败。<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>。传入URL中的其他变体也是可能的。例如，它可能包含路径遍历序列（如`/../`）或多个前向斜杠（`//`），这也可能导致模式匹配失败。一些容器在执行 Servlet 映射之前将这些规范化，但其他容器则不这样做。为了防止此类问题，`FilterChainProxy`使用`HttpFirewall`策略来检查和包装请求。默认情况下，未规范化的请求会被自动拒绝，并且为了匹配的目的，会删除路径参数和重复的斜杠。<sup class="footnote">[<a id="_footnoteref_3" class="footnote" href="#_footnotedef_3" title="View footnote.">3</a>]</sup>。因此，使用`FilterChainProxy`来管理安全筛选链是非常重要的。请注意，`servletPath`和`pathInfo`值是由容器解码的，因此你的应用程序不应该有任何包含半冒号的有效路径，因为为了匹配的目的，这些部分将被删除。

如上所述，默认策略是使用 Ant 样式的路径进行匹配，这可能是大多数用户的最佳选择。该策略在类`AntPathRequestMatcher`中实现，该类使用 Spring 的`AntPathMatcher`对串联的`servletPath`和`pathInfo`执行不区分大小写的模式匹配，忽略`queryString`。

如果出于某种原因，你需要一个更强大的匹配策略，那么可以使用正则表达式。那么策略执行`RegexRequestMatcher`。有关此类的更多信息，请参见Javadoc。

在实践中，我们建议你在服务层使用方法安全性来控制对应用程序的访问，而不是完全依赖于在Web-Application级别定义的安全约束的使用。URL会发生变化，很难考虑到应用程序可能支持的所有可能的URL，以及如何操纵请求。你应该试着把自己限制在几条简单易懂的路径上。始终尝试使用“默认拒绝”方法，其中你有一个包罗万象的通配符（/**或**）在最后定义并拒绝访问。

在服务层定义的安全性要强大得多，也更难绕过，因此你应该始终利用 Spring Security的方法安全性选项。

`HttpFirewall`还通过拒绝HTTP响应头中的新行字符来防止[HTTP响应拆分](https://www.owasp.org/index.php/HTTP_Response_Splitting)。

默认情况下使用`StrictHttpFirewall`。此实现拒绝似乎是恶意的请求。如果对你的需求来说过于严格，那么你可以自定义拒绝的请求类型。然而，重要的是，你要知道这可能会使你的应用程序受到攻击。例如，如果你希望利用 Spring MVC的矩阵变量，可以使用以下配置：

例1.允许矩阵变量

Java

```
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
```

XML

```
<b:bean id="httpFirewall"
    class="org.springframework.security.web.firewall.StrictHttpFirewall"
    p:allowSemicolon="true"/>

<http-firewall ref="httpFirewall"/>
```

Kotlin

```
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}
```

`StrictHttpFirewall`提供了一个允许的有效HTTP方法列表，这些方法被允许对[跨站点跟踪](https://owasp.org/www-community/attacks/Cross_Site_Tracing)和[HTTP动词篡改](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/02-Configuration_and_Deployment_Management_Testing/06-Test_HTTP_Methods)进行保护。默认的有效方法是“delete”、“get”、“head”、“options”、“patch”、“post”和“put”。如果你的应用程序需要修改有效的方法，你可以配置一个自定义的`StrictHttpFirewall` Bean。例如，以下将只允许HTTP“get”和“postmethod”方法：

例2.只允许get和post（P）

Java

```
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
```

XML

```
<b:bean id="httpFirewall"
      class="org.springframework.security.web.firewall.StrictHttpFirewall"
      p:allowedHttpMethods="GET,HEAD"/>

<http-firewall ref="httpFirewall"/>
```

Kotlin

```
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}
```

|   |如果你正在使用`new MockHttpServletRequest()`，则它当前将创建一个HTTP方法作为空字符串“。<br/>这是一个无效的HTTP方法，并且将被 Spring Security拒绝。<br/>你可以通过将其替换为`new MockHttpServletRequest("GET", "")`来解决此问题。<br/>有关请求改进此问题的请参见[SPR\_16851](https://jira.spring.io/browse/SPR-16851)。|
|---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

如果必须允许任何HTTP方法（不推荐），则可以使用`StrictHttpFirewall.setUnsafeAllowAnyHttpMethod(true)`。这将完全禁用HTTP方法的验证。

`StrictHttpFirewall`还检查头名称、值和参数名称。它要求每个字符都有一个定义的代码点，而不是一个控制字符。

可以使用以下方法根据需要放松或调整此要求：

* `StrictHttpFirewall#setAllowedHeaderNames(Predicate)`

* `StrictHttpFirewall#setAllowedHeaderValues(Predicate)`

* `StrictHttpFirewall#setAllowedParameterNames(Predicate)`

|   |而且，参数值可以用`setAllowedParameterValues(Predicate)`来控制。|
|---|-------------------------------------------------------------------------------------|

例如，要关闭此检查，你可以将你的`StrictHttpFirewall`与始终返回`Predicate`的`true`连接起来，如：

例3.允许任何头名称、头值和参数名称

Java

```
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
```

Kotlin

```
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    firewall.setAllowedHeaderNames { true }
    firewall.setAllowedHeaderValues { true }
    firewall.setAllowedParameterNames { true }
    return firewall
}
```

或者，你可能需要允许一个特定的值。

例如，iPhone X使用`User-Agent`，其中包含一个不在ISO-8859-1字符集中的字符。由于这一事实，一些应用程序服务器将把这个值解析为两个单独的字符，后者是一个未定义的字符。

你可以使用`setAllowedHeaderValues`方法来解决这个问题，如下所示：

例4.允许某些用户代理

Java

```
@Bean
public StrictHttpFirewall httpFirewall() {
    StrictHttpFirewall firewall = new StrictHttpFirewall();
    Pattern allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*");
    Pattern userAgent = ...;
    firewall.setAllowedHeaderValues((header) -> allowed.matcher(header).matches() || userAgent.matcher(header).matches());
    return firewall;
}
```

Kotlin

```
@Bean
fun httpFirewall(): StrictHttpFirewall {
    val firewall = StrictHttpFirewall()
    val allowed = Pattern.compile("[\\p{IsAssigned}&&[^\\p{IsControl}]]*")
    val userAgent = Pattern.compile(...)
    firewall.setAllowedHeaderValues { allowed.matcher(it).matches() || userAgent.matcher(it).matches() }
    return firewall
}
```

对于标头值，你可以考虑在验证时将它们解析为UTF-8，如下所示：

例5.将标题解析为UTF-8

Java

```
firewall.setAllowedHeaderValues((header) -> {
    String parsed = new String(header.getBytes(ISO_8859_1), UTF_8);
    return allowed.matcher(parsed).matches();
});
```

Kotlin

```
firewall.setAllowedHeaderValues {
    val parsed = String(header.getBytes(ISO_8859_1), UTF_8)
    return allowed.matcher(parsed).matches()
}
```

---

[1](#_footnoteref_1)。当浏览器不支持cookie并且`jsessionid`参数在分号之后的URL中附加时，你可能已经看到了这种情况。然而，RFC允许在URL的任何路径段中存在这些参数。

[2](#_footnoteref_2)。一旦请求离开`FilterChainProxy`，将返回原始值，因此应用程序仍然可用。

[3](#_footnoteref_3)。因此，例如，原始请求路径`/secure;hack=1/somefile.html;hack=2`将返回为`/secure/somefile.html`。

[HTTP](http.html)[整合](../integrations/index.html)

