ServerWebExchangeFirewall

恶意用户可以通过多种方式创建请求,从而利用应用程序。Spring Security 提供了 ServerWebExchangeFirewall 来拒绝那些看起来具有恶意的请求。默认实现是 StrictServerWebExchangeFirewall,它会拒绝恶意请求。

例如,请求可能包含路径遍历序列(如 /../)或多个正斜杠 (//),这些也可能导致模式匹配失败。有些容器在执行 servlet 映射之前会对其进行规范化处理,但有些则不会。为了防范此类问题,WebFilterChainProxy 使用 ServerWebExchangeFirewall 策略来检查和封装请求。默认情况下,未规范化的请求会被自动拒绝,并且会移除路径参数以进行匹配。(例如,原始请求路径 /secure;hack=1/somefile.html;hack=2 将被返回为 /secure/somefile.html。)因此,使用 WebFilterChainProxy 至关重要。

实际上,我们建议您在服务层使用方法安全来控制应用程序的访问,而不是完全依赖于在 Web 应用程序级别定义的安全性约束。URL 会发生变化,而且很难考虑到应用程序可能支持的所有可能的 URL 以及请求可能被操纵的方式。您应该限制自己使用一些易于理解的简单模式。始终尝试使用“默认拒绝”的方法,在最后定义一个包罗万象的通配符 (/ 或 ``**``) 来拒绝访问。

在服务层定义的安全性更加健壮且更难绕过,因此您应该始终利用 Spring Security 的方法安全选项。

您可以通过将其暴露为一个 Bean 来定制 ServerWebExchangeFirewall

允许矩阵变量
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowSemicolon(true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowSemicolon(true)
    return firewall
}

为了防御 跨站追踪 (XST)HTTP 动词篡改StrictServerWebExchangeFirewall 提供了一个允许的有效 HTTP 方法列表。默认的有效方法是 DELETEGETHEADOPTIONSPATCHPOSTPUT。如果您的应用程序需要修改有效方法,可以配置一个自定义的 StrictServerWebExchangeFirewall Bean。以下示例仅允许 HTTP GETPOST 方法

仅允许 GET 和 POST
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowedHttpMethods(Arrays.asList("GET", "POST"));
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowedHttpMethods(listOf("GET", "POST"))
    return firewall
}

如果您必须允许任何 HTTP 方法(不推荐),可以使用 StrictServerWebExchangeFirewall.setUnsafeAllowAnyHttpMethod(true)。这样做会完全禁用对 HTTP 方法的验证。

StrictServerWebExchangeFirewall 还会检查 header 名称和值以及参数名称。它要求每个字符都有一个定义的码点,而不是控制字符。

可以通过使用以下方法根据需要放宽或调整此要求

  • StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)

  • StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)

  • StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)

参数值也可以通过 setAllowedParameterValues(Predicate) 来控制。

例如,要关闭此检查,您可以将您的 StrictServerWebExchangeFirewall 配置为始终返回 truePredicate 实例

允许任何 Header 名称、Header 值和参数名称
  • Java

  • Kotlin

@Bean
public StrictServerWebExchangeFirewall httpFirewall() {
    StrictServerWebExchangeFirewall firewall = new StrictServerWebExchangeFirewall();
    firewall.setAllowedHeaderNames((header) -> true);
    firewall.setAllowedHeaderValues((header) -> true);
    firewall.setAllowedParameterNames((parameter) -> true);
    return firewall;
}
@Bean
fun httpFirewall(): StrictServerWebExchangeFirewall {
    val firewall = StrictServerWebExchangeFirewall()
    firewall.setAllowedHeaderNames { true }
    firewall.setAllowedHeaderValues { true }
    firewall.setAllowedParameterNames { true }
    return firewall
}

另外,可能存在您需要允许的特定值。

例如,iPhone Xʀ 使用的 User-Agent 包含一个不在 ISO-8859-1 字符集中的字符。由于此原因,一些应用程序服务器会将此值解析为两个独立的字符,其中后者是一个未定义的字符。

您可以使用 setAllowedHeaderValues 方法来解决此问题

允许某些 User Agent
  • Java

  • Kotlin

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

对于 header 值,您也可以考虑在验证时将其解析为 UTF-8

将 Header 解析为 UTF-8
  • Java

  • Kotlin

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

ServerExchangeRejectedHandler 接口用于处理 Spring Security 的 ServerWebExchangeFirewall 抛出的 ServerExchangeRejectedException。默认情况下,HttpStatusExchangeRejectedHandler 用于在请求被拒绝时向客户端发送 HTTP 400 响应。要定制行为,用户可以暴露一个 ServerExchangeRejectedHandler Bean。例如,当请求被拒绝时,以下代码将发送 HTTP 404

请求被拒绝时发送 404
  • Java

  • Kotlin

@Bean
ServerExchangeRejectedHandler rejectedHandler() {
	return new HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND);
}
@Bean
fun rejectedHandler(): ServerExchangeRejectedHandler {
    return HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND)
}

可以通过创建自定义的 ServerExchangeRejectedHandler 实现来完全定制处理逻辑。