ServerWebExchangeFirewall
恶意用户可以通过多种方式创建请求来利用应用程序。Spring Security 提供了 ServerWebExchangeFirewall
以允许拒绝看起来恶意的请求。默认实现是 StrictServerWebExchangeFirewall
,它会拒绝恶意请求。
例如,请求可能包含路径遍历序列(如 /../
)或多个正斜杠(//
),这可能导致模式匹配失败。有些容器在执行servlet映射之前会将这些规范化,但有些则不会。为了防止这些问题,WebFilterChainProxy
使用 ServerWebExchangeFirewall
策略来检查和包装请求。默认情况下,未规范化的请求会被自动拒绝,并且为了匹配的目的,路径参数会被移除。(因此,例如,原始请求路径 /secure;hack=1/somefile.html;hack=2
会被返回为 /secure/somefile.html
。)因此,使用 WebFilterChainProxy
是非常重要的。
在实践中,我们建议你在服务层使用方法安全性来控制对应用程序的访问,而不是完全依赖于在 Web 应用程序级别定义的安全约束。URL 会发生变化,并且很难考虑到应用程序可能支持的所有可能的 URL 以及请求可能被如何操纵。你应该限制自己只使用一些易于理解的简单模式。始终尝试使用“默认拒绝”的方法,在最后定义一个包罗万象的通配符(/
or )来拒绝访问。
在服务层定义的安全性要更加健壮且难以绕过,因此你应该始终利用 Spring Security 的方法安全选项。
你可以通过将 ServerWebExchangeFirewall
暴露为一个 Bean 来自定义它。
- 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 方法列表。默认的有效方法是 DELETE
、GET
、HEAD
、OPTIONS
、PATCH
、POST
和 PUT
。如果您的应用程序需要修改有效方法,您可以配置一个自定义的 StrictServerWebExchangeFirewall
bean。以下示例仅允许 HTTP 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
也会检查头部名称和值以及参数名称。它要求每个字符都有一个定义的码点并且不是控制字符。
此要求可以使用以下方法根据需要进行放宽或调整:
-
StrictServerWebExchangeFirewall#setAllowedHeaderNames(Predicate)
-
StrictServerWebExchangeFirewall#setAllowedHeaderValues(Predicate)
-
StrictServerWebExchangeFirewall#setAllowedParameterNames(Predicate)
参数值也可以通过 setAllowedParameterValues(Predicate)
进行控制。
例如,要关闭此检查,可以使用始终返回 true
的 Predicate
实例来连接你的 StrictServerWebExchangeFirewall
:
- 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
方法来解决这个问题:
- 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
}
在处理头部值时,你可以在验证时考虑将它们解析为 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:
@Bean
public ServerExchangeRejectedHandler exchangeRejectedHandler() {
return (exchange, ex) -> exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
}
- Java
- Kotlin
@Bean
ServerExchangeRejectedHandler rejectedHandler() {
return new HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND);
}
@Bean
fun rejectedHandler(): ServerExchangeRejectedHandler {
return HttpStatusExchangeRejectedHandler(HttpStatus.NOT_FOUND)
}
处理可以通过创建自定义的 ServerExchangeRejectedHandler
实现来完全定制。