Filters

  • Form 数据:允许使用非标准 HTTP 方法(如 PUT、DELETE)提交表单数据,使其更易于与非浏览器客户端交互。

  • 转发标头:修改传入请求,允许应用程序根据转发代理提供的标头设置主机、端口和方案。

  • 浅层 ETag:创建一个“浅层”ETag,节省带宽,但没有节省 CPU。它通过将响应内容的 MD5 哈希与客户端提供的 If-None-Match 请求标头进行比较来检测缓存。

  • CORS:在控制器级别提供细粒度的 CORS 配置,但与 Spring Security 一起使用时,建议使用内置的 CorsFilter 来简化集成。

spring-web 模块提供了一些有用的过滤器:

Form Data

浏览器只能通过 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*() 系列方法提供表单数据。

Forwarded Headers

Non-standard Headers

当请求经过负载平衡器等代理时,主机、端口和协议可能会更改,而且这对于从客户端角度创建指向正确的主机、端口和协议的链接来说是个挑战。

RFC 7239 定义了 Forwarded HTTP 标头,代理可以使用它来提供有关原始请求的信息。

Non-standard Headers

也有一些其他非标准头,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

X-Forwarded-Host

虽然不是标准,但 X-Forwarded-Host: <host>是一个事实上的标准标头,用于将原始主机传达给下游服务器。例如,如果将 https://example.com/resource 的请求发送到将请求转发给 http://localhost:8080/resource 的代理,那么可以发送 X-Forwarded-Host: example.com 的标头以告知服务器原始主机是 example.com

X-Forwarded-Port

虽然不是标准,X-Forwarded-Port: <port>`是用于向服务器传递原始端口的实际标准头信息。例如,如果发送`https://example.com/resource`请求到将请求转发到`http://localhost:8080/resource`的代理,那么可以发送`X-Forwarded-Port: 443`头信息来通知服务器原始端口为`443

X-Forwarded-Proto

虽然不是标准,但 X-Forwarded-Proto: (https|http)是一个事实上的标准标头,用于将原始协议(例如 https/https)传达给下游服务器。例如,如果将 https://example.com/resource 的请求发送到将请求转发给 http://localhost:8080/resource 的代理,那么可以发送 X-Forwarded-Proto: https 的标头以告知服务器原始协议是 https

X-Forwarded-Ssl

虽然不是标准,X-Forwarded-Ssl: (on|off)`是用于向服务器传递原始协议(例如https/https)的实际标准头信息。例如,如果发送`https://example.com/resource`请求到将请求转发到`http://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} -> http://localhost:8080/app1/{path}

前缀是捕获组 {path} 之前的路径开头。对于代理,前缀是`/api`,而对于服务器,前缀是`/app1`。在这种情况下,代理可以发送`X-Forwarded-Prefix: /api`,让原始前缀`/api`覆盖服务器前缀`/app1`。

场景2:删除路径前缀

有时,应用程序可能希望删除前缀。例如,考虑以下代理到服务器的映射:

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

代理没有前缀,而应用程序`app1`和`app2`的路径前缀分别为`/app1`和`/app2`。代理可以发送`X-Forwarded-Prefix: ,让空前缀覆盖服务器前缀/app1`和`/app2`。

这种情况的常见部署场景是,每个生产应用程序服务器都要付费获得许可证,最好在每个服务器上部属于多个应用程序以减少费用。另一个原因是在同一服务器上运行更多应用程序,以便共享服务器运行所需的资源。 在这些场景中,应用程序需要一个非空上下文根,因为同一服务器上存在多个应用程序。然而,这不应在应用程序可能使用不同子域的公开API 的URL路径中可见,这些子域提供的优点包括:

  • 增加安全性,例如相同的源策略

  • 应用程序的独立扩展(不同的域指向不同的 IP 地址)

场景3:插入路径前缀

在其他情况下,可能需要前置前缀。例如,考虑以下代理到服务器的映射:

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

在这种情况下,代理的前缀为`/api/app1`,服务器的前缀为`/app1`。代理可以发送`X-Forwarded-Prefix: /api/app1`,让原始前缀`/api/app1`覆盖服务器前缀`/app1`。

ForwardedHeaderFilter

ForwardedHeaderFilter 是一种 Servlet 过滤器,用于根据 Forwarded 标头来修改请求(a)更改主机、端口和方案,以及(b)移除这些标头以消除进一步的影响。该过滤器依赖于封装请求,因此必须排在其他过滤器之前,例如 RequestContextFilter,而该过滤器应该使用修改后的请求,而不是使用原始请求。

Security Considerations

需要考虑转发标头所涉及的安全因素,因为应用程序无法得知这些标头是由代理按照预期添加的,还是由恶意客户端添加的。这就是位于信任边界处的代理应该被配置为移除从外部传入的不可信 Forwarded 标头的缘故。您还可以使用 removeOnly=true 来配置 ForwardedHeaderFilter,在这种情况下,它会移除标头,但不会使用这些标头。

Dispatcher Types

为了支持asynchronous requests和错误调度,此过滤器应当与`DispatcherType.ASYNC`和`DispatcherType.ERROR`一起映射。如果使用 Spring Framework 的`AbstractAnnotationConfigDispatcherServletInitializer`(请参阅Servlet Config),则会为所有调度类型自动注册所有过滤器。然而,如果通过`web.xml`或在 Spring Boot 中通过`FilterRegistrationBean`注册过滤器,请务必在`DispatcherType.REQUEST`之外包含`DispatcherType.ASYNC`和`DispatcherType.ERROR`。

Shallow ETag

ShallowEtagHeaderFilter 过滤器创建一个“浅层”Etag,方法是缓存写入响应的内容并由此计算一个 MD5 哈希。下次客户端发送请求时,它会执行相同的操作,但它还会将计算出的值与 If-None-Match 请求标头进行比较,并且这两个值相同时返回 304(NOT_MODIFIED)。

此策略可节省网络带宽,但不能节省 CPU,因为必须针对每个请求计算出完整响应。 状态更改 HTTP 方法以及其他 HTTP 条件请求头(如`If-Match`和`If-Unmodified-Since`)超出了此过滤器的范围。控制器级别的其他策略可以避免计算,并且对 HTTP 条件请求的兼容性更强。请参阅HTTP Caching

此过滤器有一个`writeWeakETag`参数,用于将过滤器配置为写入类似于以下内容的弱 ETag:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(如https://datatracker.ietf.org/doc/html/rfc7232#section-2.3[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 Filter 的部分,了解更多详情。