過濾器
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-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-Ssl
和 X-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}
代理沒有字首,而應用程式 app1
和 app2
分別具有路徑字首 /app1
和 /app2
。代理可以傳送 X-Forwarded-Prefix:
,以便空字首覆蓋伺服器字首 /app1
和 /app2
。
這種部署場景的一個常見情況是按生產應用伺服器支付許可費用,為了降低費用,最好在每臺伺服器上部署多個應用。另一個原因是在同一臺伺服器上執行更多應用,以共享伺服器執行所需的資源。 在這些場景中,應用需要一個非空的上下文根,因為同一臺伺服器上執行著多個應用。然而,這不應出現在公共 API 的 URL 路徑中,公共 API 中應用可以使用不同的子域,從而帶來以下好處:
|
場景 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.ASYNC
和 DispatcherType.ERROR
。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer
(參見Servlet 配置),所有過濾器會自動註冊到所有分發型別。但是,如果透過 web.xml
或在 Spring Boot 中透過 FilterRegistrationBean
註冊過濾器,請務必在 DispatcherType.REQUEST
之外包含 DispatcherType.ASYNC
和 DispatcherType.ERROR
。
淺層 ETag
ShallowEtagHeaderFilter
過濾器透過快取寫入響應的內容並計算其 MD5 雜湊來建立“淺層” ETag。下次客戶端傳送請求時,它也會這樣做,但同時會將計算出的值與 If-None-Match
請求頭進行比較,如果兩者相等,則返回 304 (NOT_MODIFIED)。
這種策略節省了網路頻寬,但不節省 CPU,因為每個請求都必須計算完整的響應。修改狀態的 HTTP 方法以及其他 HTTP 條件請求頭(如 If-Match
和 If-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 過濾器鏈之前。
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()