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 實現來完全定製處理邏輯。