对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

过滤器

表单数据

浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器客户端还可以使用 HTTP PUT、PATCH 和 DELETE。Servlet API 要求 ServletRequest.getParameter*() 方法仅对 HTTP POST 支持表单字段的访问。spring-doc.cadn.net.cn

spring-web 模块提供了 FormContentFilter,用于拦截内容类型为 application/x-www-form-urlencoded 的 HTTP PUT、PATCH 和 DELETE 请求,从请求体中读取表单数据,并包装 ServletRequest,使得可以通过 ServletRequest.getParameter*() 系列方法获取表单数据。spring-doc.cadn.net.cn

转发的请求头

当请求经过负载均衡器等代理时,主机、端口和协议方案可能会发生变化,这使得从客户端视角创建指向正确主机、端口和协议方案的链接变得具有挑战性。spring-doc.cadn.net.cn

RFC 7239 定义了 Forwarded HTTP 请求头, 代理可以使用该请求头来提供有关原始请求的信息。spring-doc.cadn.net.cn

非标准头信息

还有其他一些非标准的头部字段,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefixspring-doc.cadn.net.cn

X-Forwarded-Host

虽然并非标准,但 X-Forwarded-Host: <host> 是一个事实上的标准请求头,用于将原始主机信息传达给下游服务器。例如,如果将针对 example.com/resource 的请求发送到代理,而该代理将请求转发到 localhost:8080/resource,则可以发送一个 X-Forwarded-Host: example.com 请求头,以告知服务器原始主机为 example.comspring-doc.cadn.net.cn

X-Forwarded-Port

虽然不是标准规范,但 X-Forwarded-Port: <port> 是一种事实上的标准请求头,用于将原始端口信息传递给下游服务器。例如,如果一个对 example.com/resource 的请求被发送到某个代理服务器,该代理服务器再将请求转发至 localhost:8080/resource,那么可以附带一个 X-Forwarded-Port: 443 请求头,以告知目标服务器原始请求的端口是 443spring-doc.cadn.net.cn

X-Forwarded-Proto

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

X-Forwarded-Ssl

虽然不是标准规范,但 X-Forwarded-Ssl: (on|off) 是一种事实上的标准请求头,用于向下游服务器传递原始协议(例如 https / http)。例如,如果一个对 example.com/resource 的请求被发送到一个代理服务器,该代理将请求转发至 localhost:8080/resource,那么代理会添加 X-Forwarded-Ssl: on 头部,以告知后端服务器原始协议是 httpsspring-doc.cadn.net.cn

X-Forwarded-Prefix

虽然并非标准,但 X-Forwarded-Prefix: <prefix> 是一个事实上的标准请求头,用于将原始 URL 路径前缀传达给下游服务器。spring-doc.cadn.net.cn

X-Forwarded-Prefix 的使用会因部署场景而异,需要具备灵活性,以便替换、移除或在目标服务器的路径前缀前添加内容。spring-doc.cadn.net.cn

场景1:覆盖路径前缀spring-doc.cadn.net.cn

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

前缀是捕获组 {path} 之前路径的起始部分。对于代理来说,前缀是 /api,而对于服务器来说,前缀是 /app1。在这种情况下,代理可以发送 X-Forwarded-Prefix: /api,以使原始前缀 /api 覆盖服务器前缀 /app1spring-doc.cadn.net.cn

场景2:移除路径前缀spring-doc.cadn.net.cn

有时,应用程序可能希望移除该前缀。例如,考虑以下代理到服务器的映射:spring-doc.cadn.net.cn

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

代理没有前缀,而应用程序 app1app2 分别具有路径前缀 /app1/app2。代理可以发送 X-Forwarded-Prefix: , 以使用空前缀覆盖服务器上的前缀 /app1/app2spring-doc.cadn.net.cn

这种部署场景的一个常见情况是,许可证按生产应用服务器收费,因此更倾向于在每台服务器上部署多个应用程序以降低费用。另一个原因是在同一台服务器上运行更多应用程序,以便共享服务器运行所需的资源。spring-doc.cadn.net.cn

在这些场景中,应用程序需要一个非空的上下文根路径,因为同一服务器上部署了多个应用程序。然而,在公共 API 的 URL 路径中不应显示该上下文根路径,因为应用程序可能会使用不同的子域名,这样可以带来如下好处:spring-doc.cadn.net.cn

场景3:插入路径前缀spring-doc.cadn.net.cn

在其他情况下,可能需要添加前缀。例如,考虑以下代理到服务器的映射:spring-doc.cadn.net.cn

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

在这种情况下,代理的前缀为 /api/app1,而服务器的前缀为 /app1。代理可以发送 X-Forwarded-Prefix: /api/app1,以使原始前缀 /api/app1 覆盖服务器前缀 /app1spring-doc.cadn.net.cn

ForwardedHeaderFilter

ForwardedHeaderFilter 是一个 Servlet 过滤器,用于修改请求,以实现以下两个目的: a) 根据 Forwarded 头信息更改主机、端口和协议(scheme),以及 b) 移除这些头信息,以避免后续产生影响。该过滤器通过包装请求来实现功能,因此必须排在其他过滤器(例如 RequestContextFilter)之前,以确保这些过滤器能够处理修改后的请求,而不是原始请求。spring-doc.cadn.net.cn

安全注意事项

由于应用程序无法判断转发头(forwarded headers)是由代理按预期添加的,还是由恶意客户端伪造的,因此在使用转发头时存在安全方面的考量。这就是为什么应在信任边界的代理上进行配置,以移除来自外部的不可信 Forwarded 头信息。你也可以将 ForwardedHeaderFilter 配置为 removeOnly=true,在这种情况下,该过滤器会移除这些头信息但不会使用它们。spring-doc.cadn.net.cn

调度器类型

为了支持异步请求和错误分发,此过滤器应映射到DispatcherType.ASYNC以及DispatcherType.ERROR。 如果使用 Spring Framework 的AbstractAnnotationConfigDispatcherServletInitializer (请参阅Servlet 配置),所有过滤器将自动为所有分发类型注册。但是,如果通过web.xml注册过滤器,或在 Spring Boot 中通过FilterRegistrationBean注册,请确保除了DispatcherType.REQUEST之外,还包含DispatcherType.ASYNCDispatcherType.ERRORspring-doc.cadn.net.cn

浅层ETag

ShallowEtagHeaderFilter 过滤器通过缓存写入响应的内容并从中计算 MD5 哈希值来创建一个“浅层”ETag。下一次客户端发送请求时,它会执行相同的操作,但还会将计算出的值与 If-None-Match 请求头进行比较,如果两者相等,则返回 304(NOT_MODIFIED)。spring-doc.cadn.net.cn

该策略节省了网络带宽,但并未节省 CPU 资源,因为每次请求都必须完整计算响应内容。 状态变更的 HTTP 方法以及其他 HTTP 条件请求头(例如 If-MatchIf-Unmodified-Since)不在本过滤器的处理范围内。在控制器层面采用其他策略 可以避免进行完整计算,并对 HTTP 条件请求提供更广泛的支持。 参见 HTTP 缓存spring-doc.cadn.net.cn

该过滤器具有一个 writeWeakETag 参数,用于配置过滤器以写入弱 ETag, 类似于以下格式:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(如 RFC 7232 第 2.3 节 中所定义)。spring-doc.cadn.net.cn

为了支持异步请求,此过滤器必须使用DispatcherType.ASYNC进行映射,以便过滤器能够延迟并成功生成 ETag 直到最后一次异步调度结束。如果使用 Spring Framework 的AbstractAnnotationConfigDispatcherServletInitializer(请参阅Servlet 配置),所有过滤器将自动为所有调度类型注册。但是,如果通过web.xml注册过滤器,或在 Spring Boot 中通过FilterRegistrationBean注册,请务必包含DispatcherType.ASYNCspring-doc.cadn.net.cn

CORS(跨域资源共享)

Spring MVC 通过控制器上的注解提供了细粒度的 CORS 配置支持。然而,当与 Spring Security 一起使用时,我们建议依赖内置的 CorsFilter,该过滤器必须排在 Spring Security 过滤器链之前。spring-doc.cadn.net.cn

有关更多详细信息,请参阅CORSCORS 过滤器部分。spring-doc.cadn.net.cn