過濾器

spring-web 模組提供了一些有用的過濾器

Servlet 過濾器可以在 web.xml 配置檔案中配置,或使用 Servlet 註解。如果您使用 Spring Boot,您可以將它們宣告為 Bean 並作為應用程式的一部分進行配置

表單資料

瀏覽器只能透過 HTTP GET 或 HTTP POST 提交表單資料,但非瀏覽器客戶端也可以使用 HTTP PUT、PATCH 和 DELETE。Servlet API 要求 ServletRequest.getParameter*() 方法僅支援 HTTP POST 的表單欄位訪問。

spring-web 模組提供了 FormContentFilter,用於攔截內容型別為 application/x-www-form-urlencoded 的 HTTP PUT、PATCH 和 DELETE 請求,從請求體讀取表單資料,幷包裝 ServletRequest 使表單資料可以透過 ServletRequest.getParameter*() 方法族獲取。

轉發頭

當請求透過負載均衡器等代理時,主機、埠和方案可能會發生變化,這使得從客戶端的角度建立指向正確主機、埠和方案的連結成為一項挑戰。

RFC 7239 定義了 Forwarded HTTP 頭,代理可以使用它來提供有關原始請求的資訊。

非標準頭

還有其他一些非標準頭,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

X-Forwarded-Host

雖然不是標準,但 X-Forwarded-Host: <host> 是一個事實標準頭,用於將原始主機傳遞給下游伺服器。例如,如果將對 example.com/resource 的請求傳送到代理,代理將請求轉發到 localhost:8080/resource,則可以傳送 X-Forwarded-Host: example.com 頭來通知伺服器原始主機是 example.com

X-Forwarded-Port

雖然不是標準,但 X-Forwarded-Port: <port> 是一個事實標準頭,用於將原始埠傳遞給下游伺服器。例如,如果將對 example.com/resource 的請求傳送到代理,代理將請求轉發到 localhost:8080/resource,則可以傳送 X-Forwarded-Port: 443 頭來通知伺服器原始埠是 443

X-Forwarded-Proto

雖然不是標準,但 X-Forwarded-Proto: (https|http) 是一個事實標準頭,用於將原始協議(例如 https / https)傳遞給下游伺服器。例如,如果將對 example.com/resource 的請求傳送到代理,代理將請求轉發到 localhost:8080/resource,則可以傳送 X-Forwarded-Proto: https 頭來通知伺服器原始協議是 https

X-Forwarded-Ssl

雖然不是標準,但 X-Forwarded-Ssl: (on|off) 是一個事實標準頭,用於將原始協議(例如 https / https)傳遞給下游伺服器。例如,如果將對 example.com/resource 的請求傳送到代理,代理將請求轉發到 localhost:8080/resource,則可以傳送 X-Forwarded-Ssl: on 來通知伺服器原始協議是 https

X-Forwarded-Prefix

雖然不是標準,但 X-Forwarded-Prefix: <prefix> 是一個事實標準頭,用於將原始 URL 路徑字首傳遞給下游伺服器。

X-Forwarded-Prefix 的使用因部署場景而異,需要靈活地允許替換、移除或附加目標伺服器的路徑字首。

場景 1:覆蓋路徑字首

https://example.com/api/{path} -> https://:8080/app1/{path}

字首是捕獲組 {path} 之前的路徑的開頭。對於代理,字首是 /api,而對於伺服器,字首是 /app1。在這種情況下,代理可以傳送 X-Forwarded-Prefix: /api,以便原始字首 /api 覆蓋伺服器字首 /app1

場景 2:移除路徑字首

有時,應用程式可能希望移除字首。例如,考慮以下代理到伺服器的對映:

https://app1.example.com/{path} -> https://:8080/app1/{path}
https://app2.example.com/{path} -> https://:8080/app2/{path}

代理沒有字首,而應用程式 app1app2 分別具有路徑字首 /app1/app2。代理可以傳送 X-Forwarded-Prefix: ,以便空字首覆蓋伺服器字首 /app1/app2

這種部署場景的一個常見情況是按生產應用伺服器支付許可費用,為了降低費用,最好在每臺伺服器上部署多個應用。另一個原因是在同一臺伺服器上執行更多應用,以共享伺服器執行所需的資源。

在這些場景中,應用需要一個非空的上下文根,因為同一臺伺服器上執行著多個應用。然而,這不應出現在公共 API 的 URL 路徑中,公共 API 中應用可以使用不同的子域,從而帶來以下好處:

  • 增強安全性,例如,同源策略

  • 應用程式的獨立伸縮(不同域名指向不同 IP 地址)

場景 3:插入路徑字首

在其他情況下,可能需要附加一個字首。例如,考慮以下代理到伺服器的對映:

https://example.com/api/app1/{path} -> https://:8080/app1/{path}

在這種情況下,代理的字首是 /api/app1,伺服器的字首是 /app1。代理可以傳送 X-Forwarded-Prefix: /api/app1,以便原始字首 /api/app1 覆蓋伺服器字首 /app1

ForwardedHeaderFilter

ForwardedHeaderFilter 是一個 Servlet 過濾器,它修改請求,以便 a) 根據 Forwarded 頭更改主機、埠和方案,b) 移除這些頭以消除進一步的影響。該過濾器依賴於包裝請求,因此必須排在其他過濾器(如 RequestContextFilter)之前,這些過濾器應處理修改後的請求而不是原始請求。

安全注意事項

對於轉發頭,存在安全方面的考慮,因為應用程式無法知道這些頭是按預期由代理新增的,還是由惡意客戶端新增的。因此,應配置信任邊界上的代理來移除來自外部的不可信 Forwarded 頭。您也可以將 ForwardedHeaderFilter 配置為 removeOnly=true,在這種情況下,它會移除但不使用這些頭。

分發器型別

為了支援非同步請求和錯誤分發,此過濾器應對映到 DispatcherType.ASYNCDispatcherType.ERROR。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer(參見Servlet 配置),所有過濾器會自動註冊到所有分發型別。但是,如果透過 web.xml 或在 Spring Boot 中透過 FilterRegistrationBean 註冊過濾器,請務必在 DispatcherType.REQUEST 之外包含 DispatcherType.ASYNCDispatcherType.ERROR

淺層 ETag

ShallowEtagHeaderFilter 過濾器透過快取寫入響應的內容並計算其 MD5 雜湊來建立“淺層” ETag。下次客戶端傳送請求時,它也會這樣做,但同時會將計算出的值與 If-None-Match 請求頭進行比較,如果兩者相等,則返回 304 (NOT_MODIFIED)。

這種策略節省了網路頻寬,但不節省 CPU,因為每個請求都必須計算完整的響應。修改狀態的 HTTP 方法以及其他 HTTP 條件請求頭(如 If-MatchIf-Unmodified-Since)超出了此過濾器的範圍。控制器級別的其他策略可以避免計算,並對 HTTP 條件請求提供更廣泛的支援。參見HTTP 快取

此過濾器有一個 writeWeakETag 引數,該引數配置過濾器寫入類似於以下內容的弱 ETag:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(如RFC 7232 第 2.3 節中所定義)。

為了支援非同步請求,此過濾器必須對映到 DispatcherType.ASYNC,以便過濾器可以延遲並在最後一個非同步分發結束時成功生成 ETag。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer(參見Servlet 配置),所有過濾器會自動註冊到所有分發型別。但是,如果透過 web.xml 或在 Spring Boot 中透過 FilterRegistrationBean 註冊過濾器,請務必包含 DispatcherType.ASYNC

CORS

Spring MVC 透過控制器上的註解提供了細粒度的 CORS 配置支援。然而,當與 Spring Security 一起使用時,我們建議依賴內建的 CorsFilter,它必須排在 Spring Security 過濾器鏈之前。

有關更多詳細資訊,請參見CORSCORS 過濾器部分。

URL Handler

在之前的 Spring Framework 版本中,Spring MVC 可以配置為在將傳入請求對映到控制器方法時忽略 URL 路徑中的尾隨斜槓。這可以透過在 PathMatchConfigurer 上啟用 setUseTrailingSlashMatch 選項來實現。這意味著傳送 "GET /home/" 請求將由使用 @GetMapping("/home") 註解的控制器方法處理。

此選項已棄用,但仍要求應用程式以安全的方式處理此類請求。UrlHandlerFilter Servlet 過濾器就是為此目的而設計的。它可以配置為:

  • 在接收帶有尾隨斜槓的 URL 時,響應 HTTP 重定向狀態,將瀏覽器重定向到不帶尾隨斜槓的 URL 變體。

  • 包裝請求,使其表現得如同請求不帶尾隨斜槓一樣,並繼續處理請求。

以下是如何為一個部落格應用例項化和配置 UrlHandlerFilter

  • Java

  • Kotlin

UrlHandlerFilter urlHandlerFilter = UrlHandlerFilter
		// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
		.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
		// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
		.trailingSlashHandler("/admin/**").wrapRequest()
		.build();
val urlHandlerFilter = UrlHandlerFilter
		// will HTTP 308 redirect "/blog/my-blog-post/" -> "/blog/my-blog-post"
		.trailingSlashHandler("/blog/**").redirect(HttpStatus.PERMANENT_REDIRECT)
		// will wrap the request to "/admin/user/account/" and make it as "/admin/user/account"
		.trailingSlashHandler("/admin/**").wrapRequest()
		.build()