过滤器

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-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-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}

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

这种部署场景的一个常见情况是,许可证按生产应用程序服务器付费,最好每个服务器部署多个应用程序以降低费用。另一个原因是在同一服务器上运行更多应用程序,以共享服务器运行所需的资源。

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

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

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

场景 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头。您还可以将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 路径中的尾部斜杠。这可以通过在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()