過濾器

在 Servlet API 中,您可以新增一個 jakarta.servlet.Filter 來在過濾器和目標 Servlet 的其餘處理鏈之前和之後應用攔截式邏輯。

spring-web 模組有許多內建的 Filter 實現

還有用於 Spring 應用程式的基礎類實現

  • GenericFilterBean — 配置為 Spring Bean 的 Filter 的基類;與 Spring ApplicationContext 生命週期整合。

  • OncePerRequestFilter — GenericFilterBean 的擴充套件,支援在請求開始時進行單次呼叫,即在 REQUEST 排程階段,並忽略透過 FORWARD 排程的進一步處理。該過濾器還控制 Filter 是否參與 ASYNCERROR 排程。

Servlet 過濾器可以在 web.xml 中配置或透過 Servlet 註解配置。在 Spring Boot 應用程式中,您可以 將 Filter 宣告為 Bean,Boot 將配置它們。

表單資料

瀏覽器只能透過 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-PrefixX-Forwarded-For

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 / http)傳達給下游伺服器。例如,如果請求 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 路徑中可見,因為應用程式可能使用不同的子域,這提供了以下好處:

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

  • 應用程式的獨立擴充套件(不同域名指向不同 IP 地址)

場景 3:插入路徑字首

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

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

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

X-Forwarded-For

X-Forwarded-For: <address> 是一個事實上的標準頭,用於將客戶端的原始 InetSocketAddress 傳達給下游伺服器。例如,如果客戶端在 [fd00:fefe:1::4] 向代理在 192.168.0.1 傳送請求,則 HTTP 請求中包含的“遠端地址”資訊將反映客戶端的實際地址,而不是代理的地址。

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 處理器

在以前的 Spring Framework 版本中,Spring MVC 可以配置為在將傳入請求對映到控制器方法時忽略 URL 路徑中的尾部斜槓。這意味著傳送“GET /home/”請求將由使用 @GetMapping("/home") 註解的控制器方法處理。

此選項在 6.0 中已棄用,在 7.0 中已刪除,但應用程式仍應以安全的方式處理此類請求。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()
© . This site is unofficial and not affiliated with VMware.