跳到主要内容
版本:7.0.3

过滤器

Hunyuan 7b 中英对照 Filters

在Servlet API中,你可以添加一个jakarta.servlet.Filter,以便在过滤器处理链的其余部分以及目标Servlet的处理之前和之后应用拦截式的逻辑。

spring-web 模块包含了许多内置的 Filter 实现:

还有用于Spring应用程序的基类实现:

  • GenericFilterBean——作为Spring bean配置的Filter的基类;与Spring ApplicationContext的生命周期集成。

  • OncePerRequestFilter——GenericFilterBean的扩展类,支持在请求开始时(即REQUEST分发阶段)仅调用一次该过滤器,并忽略通过FORWARD分发的后续处理。该过滤器还提供了对过滤器是否参与ASYNCERROR分发控制的选项。

Servlet过滤器可以在web.xml中配置,也可以通过Servlet注解进行配置。在Spring Boot应用程序中,你可以将过滤器声明为bean,Spring 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*() 系列方法获取表单数据。

转发的头部信息

当请求通过负载均衡器等代理服务器时,主机、端口和协议(scheme)可能会发生变化,这从客户端的角度来看,就使得创建指向正确主机、端口和协议的链接变得具有挑战性。

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](https://example.com/resource)被发送到一个代理服务器,而该代理服务器又将请求转发到[localhost:8080/resource](http://localhost:8080/resource),那么可以通过发送X-Forwarded-Host: example.com这一头信息来告知服务器,其原始主机是example.com

X-Forwarded-Port

虽然不是标准头,但X-Forwarded-Port: <port>实际上已成为一种行业标准,用于向下游服务器传递原始端口信息。例如,如果一个请求[example.com/resource](https://example.com/resource)被发送到一个代理服务器,而该代理服务器又将请求转发到[localhost:8080/resource](http://localhost:8080/resource),那么可以发送一个X-Forwarded-Port: 443头信息,以告知目标服务器原始端口是443

X-Forwarded-Proto

虽然不是标准头,但X-Forwarded-Proto: (https|http)实际上已成为一种行业标准头,用于向下游服务器传达原始协议(例如,https或http)。例如,如果一个对 [example.com/resource](https://example.com/resource) 的请求被发送到一个代理服务器,而该代理服务器又将请求转发到 [localhost:8080/resource](http://localhost:8080/resource),那么可以发送一个 X-Forwarded-Proto: https 头来告知服务器原始协议是 https

X-Forwarded-Ssl

虽然不是标准头,但X-Forwarded-Ssl: (on|off)实际上已经成为一种行业标准头,用于向下游服务器传递原始协议(例如,https或http)。例如,如果一个请求[example.com/resource](https://example.com/resource)被发送到一个代理,而该代理又将这个请求转发到[localhost:8080/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}

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

备注

在这种部署场景中,一个常见的情况是按照每台生产应用服务器来付费,为了减少费用,通常希望在一台服务器上部署多个应用程序。另一个原因是为了在同一台服务器上运行更多应用程序,从而共享该服务器所需的资源。

在这些场景下,由于同一台服务器上运行了多个应用程序,因此需要一个非空的上下文根(context root)。不过,这一设置不应在公共API的URL路径中显现出来,因为应用程序可以使用不同的子域名,这样做有以下好处:

  • 增强安全性,例如,可以实现同源策略(same origin policy)。
  • 实现应用程序的独立扩展(不同的域名对应不同的IP地址)。

场景3:插入路径前缀

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

https://example.com/api/app1/{path} -> http://localhost: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 头信息更改主机(host)、端口(port)和协议(scheme);b) 移除这些头信息,以避免进一步的影响。该过滤器依赖于对请求的封装处理,因此必须将其置于其他过滤器之前(如 RequestContextFilter),因为这些其他过滤器需要处理的是经过修改后的请求,而非原始请求。

安全考虑

对于转发的头部信息,存在安全方面的考虑:应用程序无法确定这些头部信息是按照预期由代理添加的,还是由恶意客户端添加的。因此,在信任边界处的代理应该被配置为移除来自外部的不可信的“Forwarded”头部信息。你还可以将“ForwardedHeaderFilter”的配置设置为“removeOnly=true”,在这种情况下,该过滤器会移除这些头部信息,但不会将其使用。

分发器类型

为了支持异步请求和错误处理,该过滤器应被映射为DispatcherType.ASYNC以及DispatcherType.ERROR。如果使用Spring Framework的AbstractAnnotationConfigDispatcherServletInitializer(参见servlet配置),所有过滤器将自动为所有调度类型进行注册。然而,如果是通过web.xml注册过滤器,或者在Spring Boot中通过FilterRegistrationBean进行注册,除了DispatcherType.REQUEST之外,务必还包括DispatcherType.ASYNCDispatcherType>Error

浅层ETag

ShallowEtagHeaderFilter 这个过滤器通过缓存写入响应的内容并计算其 MD5 哈希值来生成一个“浅层”ETag。下次客户端发送请求时,也会执行相同的操作,但同时还会将计算出的哈希值与请求头中的 If-None-Match 字段进行比较;如果两者相等,就会返回 304(未修改)状态码。

这种策略可以节省网络带宽,但无法节省CPU资源,因为每个请求都必须计算完整的响应内容。会改变状态的HTTP方法以及其他HTTP条件请求头(如If-MatchIf-Unmodified-Since)不在该过滤器的适用范围内。在控制器层面采用其他策略可以避免这种计算,同时能更好地支持HTTP条件请求。详情请参见HTTP缓存

此过滤器具有一个writeWeakETag参数,用于配置过滤器生成类似以下的弱ETag:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(如RFC 7232第2.3节中所定义)。

为了支持异步请求,此过滤器必须使用DispatcherType.ASYNC进行映射,以便过滤器可以延迟处理并在最后一个异步请求处理完成后成功生成ETag。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer(请参见servlet配置),则所有过滤器会自动为所有调度类型注册。但是,如果是通过web.xml进行过滤器注册,或者在Spring Boot中通过FilterRegistrationBean进行注册,务必包含DispatcherType.ASYNC

CORS

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

有关更多详细信息,请参阅 CORS CORS过滤器部分。

URL处理器

在之前的Spring Framework版本中,可以配置Spring MVC在映射控制器方法的请求时忽略URL路径末尾的斜杠。这意味着发送“GET /home/”请求将会由一个带有@GetMapping("/home")注解的控制器方法来处理。

此选项在6.0版本中被弃用,在7.0版本中被移除,但应用程序仍需以安全的方式处理此类请求。UrlHandlerFilter Servlet过滤器就是为此目的而设计的。它可以被配置为:

  • 当接收到带有尾随斜杠(/)的URL时,返回HTTP重定向状态码,将浏览器引导至不带尾随斜杠的URL版本。
  • 对该请求进行处理,使其仿佛没有尾随斜杠一样继续执行后续处理流程。

以下是您如何为博客应用程序实例化和配置 UrlHandlerFilter 的方法:

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();