过滤器
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} -> 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 路径中,公共 API 中应用可以使用不同的子域,从而带来以下好处:
|
场景 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 过滤器,它修改请求,以便 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()