过滤器

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

spring-web 模块有许多内置的 Filter 实现

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

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

  • OncePerRequestFilter — GenericFilterBean 的扩展,支持在请求开始时进行单次调用,即在 REQUEST 调度阶段,并忽略通过 FORWARD 调度的进一步处理。该过滤器还控制 Filter 是否参与 ASYNCERROR 调度。

Servlet 过滤器可以在 web.xml 中配置或通过 Servlet 注解配置。在 Spring Boot 应用程序中,您可以 将 Filter 声明为 Bean,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*() 系列方法提供表单数据。

转发头

当请求通过负载均衡器等代理时,主机、端口和方案可能会发生变化,这使得从客户端角度创建指向正确主机、端口和方案的链接成为一个挑战。

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 发送到代理,该代理将请求转发到 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 / http)传达给下游服务器。例如,如果请求 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://:8080/app1/{path}

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

场景 2:删除路径前缀

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

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

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

这种部署场景的一个常见情况是,每个生产应用服务器都需要支付许可证费用,并且优选在每个服务器上部署多个应用程序以减少费用。另一个原因是,为了共享服务器运行所需的资源,在同一服务器上运行更多应用程序。

在这些场景中,应用程序需要一个非空上下文根,因为同一服务器上有多个应用程序。然而,这不应在公共 API 的 URL 路径中可见,因为应用程序可能使用不同的子域,这提供了以下好处:

  • 增强安全性,例如,同源策略

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

场景 3:插入路径前缀

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

https://example.com/api/app1/{path} -> https://: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 头更改主机、端口和方案,b) 删除这些头以消除进一步的影响。该过滤器依赖于包装请求,因此它必须在其他过滤器(例如 RequestContextFilter)之前排序,这些过滤器应与修改后的请求而不是原始请求一起工作。

安全考虑

转发头存在安全考虑,因为应用程序无法知道这些头是由代理(按预期)添加的,还是由恶意客户端添加的。这就是为什么应配置信任边界处的代理以删除来自外部的不受信任的 Forwarded 头。您还可以将 ForwardedHeaderFilter 配置为 removeOnly=true,在这种情况下它会删除但不使用这些头。

调度器类型

为了支持异步请求和错误调度,此过滤器应映射到 DispatcherType.ASYNCDispatcherType.ERROR。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer(参见Servlet 配置),所有过滤器都会自动注册到所有调度类型。但是,如果通过 web.xml 或在 Spring Boot 中通过 FilterRegistrationBean 注册过滤器,请务必除了 DispatcherType.REQUEST 之外,还包括 DispatcherType.ASYNCDispatcherType.ERROR

浅层 ETag

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

这种策略节省了网络带宽,但不能节省 CPU,因为每次请求都必须计算完整的响应。状态更改的 HTTP 方法和其他 HTTP 条件请求头(如 If-MatchIf-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 过滤器链之前。

有关详细信息,请参见CORSCORS 过滤器 部分。

URL 处理器

在以前的 Spring Framework 版本中,Spring MVC 可以配置为在将传入请求映射到控制器方法时忽略 URL 路径中的尾部斜杠。这意味着发送“GET /home/”请求将由使用 @GetMapping("/home") 注解的控制器方法处理。

此选项在 6.0 中已弃用,在 7.0 中已删除,但应用程序仍应以安全的方式处理此类请求。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()
© . This site is unofficial and not affiliated with VMware.