如何在 Spring 引导中使用 Tomcat RemoteIpFilter

How to use Tomcat RemoteIpFilter in Spring Boot

Objective: 在Spring Boot web applications.

中获取用户(即request.getRemoteAddr())的远程地址

简介: 众所周知,getRemoteAddr 方法 returns 直接调用者的地址可能是代理服务器或原始用户和最终目标服务器(如果有的话)之间的任何其他服务器。要找到用户的真实地址,需要查看其他 header(例如 X-Forwarded-For)。但是我发现 Tomcat(这是我们的 Web 容器)有一个名为 RemoteIpFilter 的过滤器,它通过复制原始地址来解决这个问题,存储在另一个 header(例如 X-Forwarded-For) 返回以便调用 request.getRemoteAddr() 给你原始地址。

我在代码中添加了过滤器,一切正常:

@Bean
FilterRegistrationBean<RemoteIpFilter> remoteIpFilter() {
    val filter = new FilterRegistrationBean<>(new RemoteIpFilter());
    filter.addUrlPatterns("/*");
    return filter;
}

但是,有一个问题;当请求是多部分请求时,Spring 拒绝它:

The request was rejected because the header value "multipart/mixed; boundary="----=_Part_0_blahblah"; charset=utf-8" is not allowed.

我可以看到异常被抛出,因为 Spring 的 FilterChainProxy 过滤器将请求包装在 StrictFirewalledRequest 中,而 StrictFirewalledRequest 又对允许和不允许的内容进行了一些检查(例如上面的 content-type 是不允许的)。

除了将 RemoteIpFilter 过滤器放在 FilterChainProxy 过滤器之前,我找不到解决上述问题的合适方法:

// right before the tracing filter!
filter.setOrder(SleuthWebProperties.TRACING_FILTER_ORDER - 1);

问题

  1. 使用 RemoteIpFilter 是个好主意吗?
  2. 是否有更好的方法来解决上述问题?
  3. RemoteIpFilter 的最佳订单号是多少?

谢谢。

多亏了 Piotr P. Karwasz 的评论,我才知道如何做到这一点。这并不简单,因为它取决于环境和其他组件(例如 LB、反向代理等)。但这就是您的处理方式:

  1. 如果您使用的是最新版本的 Spring Boot + Tomcat,请不要使用 RemoteIpFilter。 Spring Boot 通过创建一个 RemoteIpValue(在 TomcatWebServerFactoryCustomizer 中)来满足您的需求。
  2. 如果您在某个云平台上进行部署,您很可能不需要执行任何操作,因为如果 Spring 检测到该平台为云平台,它会自动创建值。无需明确告诉它。 (看什么CloudPlatform.getActive(env)returns。)
  3. 如果您不在云平台上部署,可能需要至少设置以下属性之一:
    • ServerProperties#forwardHeadersStrategy = NATIVE(正如 Piotr 所建议的)
    • ServerProperties.Tomcat.Remoteip#protocolHeader(任何内容,例如 X-Forwarded-Proto
    • ServerProperties.Tomcat.Remoteip#remoteIpHeader(任何内容,例如 X-Forwarded-For
  4. 要查看它是否真的有效,请为 org.apache.catalina.valves.RemoteIpValve 启用调试日志,您应该能够看到它关于更改主机、地址等的日志。

在我们的例子中,我不需要在 Spring 上做任何事情,因为我们将应用程序部署到 Spring 创建阀门的 K8S。那为什么我没有得到正确的远程地址呢?原来我们的反向代理,Nginx的默认配置是:

proxy_set_header X-Forwarded-For        $remote_addr;

这实际上覆盖了原始值,RemoteIpValve 将使用它来设置远程地址。换句话说,阀门正确读取 X-Forwarded-For header 但 header 不包含正确的值(用户的地址)。请注意,$remote_addr 不一定是用户的地址。它可能是其他中介的地址 proxy/LB.

所以你有两个选择来解决这个问题:

  1. ServerProperties.Tomcat.Remoteip#remoteIpHeader 设置为 X-Original-Forwarded-For 因为这是 Nginx 在用 $remote_addr 覆盖之前备份 X-Forwarded-For 的原始值的地方。 (对我来说似乎是个黑客!)
  2. 将 Nginx 配置为完全不接触 X-Forwarded-For header。 (对我来说似乎是个黑客!)
  3. 配置 Nginx 以附加 X-Forwarded-For 而不是覆盖它。

要配置 Nginx 追加,您有两个选择:

  1. 处理类似这样的事情 proxy_set_header X-Forwarded-For "$remote_addr, $server_addr" 并将 Nginx 配置为追加而不是覆盖。 (有关如何在 K8S 上执行此操作,请参阅 this。虽然我无法让它工作;Nginx 抱怨 proxy-set-header 或类似内容的参数数量。我也不确定它是否是防弹解决方案)
  2. 配置您的 Helm 图表安装值:
config:
  "use-forwarded-headers": "true" # not true
  "compute-full-forwarded-for": "true" # not true

感谢 Piotr 的提示 :)

PS:还有一些关于新花式 header Forwarded 的其他注意事项应该可以解决整个混乱,但显然 Nginx 还没有完全支持它。

PSS:我没有花时间弄清楚为什么抛出异常或如何防止它。这可能是另一天:)