过滤器
表单数据
浏览器只能通过 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://127.0.0.1:8080/app1/{path}
前缀是捕获组{path}
之前的路径开头。对于代理,前缀是/api
,而对于服务器,前缀是/app1
。在这种情况下,代理可以发送X-Forwarded-Prefix: /api
,以使原始前缀/api
覆盖服务器前缀/app1
。
场景 2:移除路径前缀
有时,应用程序可能希望移除前缀。例如,考虑以下代理到服务器的映射
https://app1.example.com/{path} -> https://127.0.0.1:8080/app1/{path} https://app2.example.com/{path} -> https://127.0.0.1:8080/app2/{path}
代理没有前缀,而应用程序 app1
和 app2
分别具有路径前缀 /app1
和 /app2
。代理可以发送 X-Forwarded-Prefix:
来让空前缀覆盖服务器前缀 /app1
和 /app2
。
这种部署场景的常见情况是,许可证按生产应用程序服务器付费,并且最好在每个服务器上部署多个应用程序以减少费用。另一个原因是在同一台服务器上运行更多应用程序,以便共享服务器运行所需的资源。 在这些场景中,应用程序需要一个非空的上下文根,因为同一台服务器上有多个应用程序。但是,这在公共 API 的 URL 路径中不应该可见,应用程序可以使用不同的子域,这提供了以下好处:
|
场景 3:插入路径前缀
在其他情况下,可能需要添加前缀。例如,考虑以下代理到服务器的映射
https://example.com/api/app1/{path} -> https://127.0.0.1:8080/app1/{path}
在这种情况下,代理具有 /api/app1
的前缀,而服务器具有 /app1
的前缀。代理可以发送 X-Forwarded-Prefix: /api/app1
来让原始前缀 /api/app1
覆盖服务器前缀 /app1
。
ForwardedHeaderFilter
ForwardedHeaderFilter
是一个 Servlet 过滤器,它修改请求以 a) 根据 Forwarded
标头更改主机、端口和方案,以及 b) 移除这些标头以消除进一步的影响。该过滤器依赖于包装请求,因此它必须在其他过滤器(例如 RequestContextFilter
)之前排序,这些过滤器应该使用修改后的请求而不是原始请求。
安全注意事项
转发标头存在安全问题,因为应用程序无法知道标头是按预期由代理添加的,还是由恶意客户端添加的。这就是为什么信任边界处的代理应该配置为移除来自外部的不可信 Forwarded
标头。您还可以使用 removeOnly=true
配置 ForwardedHeaderFilter
,在这种情况下,它会移除标头,但不会使用它们。
调度程序类型
为了支持 异步请求 和错误调度,此过滤器应该使用 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
。