【问题标题】:How to use Tomcat RemoteIpFilter in Spring Boot如何在 Spring Boot 中使用 Tomcat RemoteIpFilter
【发布时间】:2021-11-22 23:32:57
【问题描述】:

目的:在 Spring Boot Web 应用中获取用户的远程地址(即request.getRemoteAddr())。

简介: 众所周知,getRemoteAddr 方法返回直接调用者的地址,该调用者可能是代理服务器或原始用户和最终目标服务器之间的任何其他服务器(如果有的话)。要找到用户的真实地址,需要查看其他标题(例如X-Forwarded-For)。但是我发现 Tomcat(这是我们的 Web 容器)有一个名为 RemoteIpFilter 的过滤器,它通过复制原始地址来解决这个问题,存储在另一个标题(例如 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 中,这反过来又检查了哪些是允许的,哪些是不允许的(例如,上述内容类型是不允许的)。

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

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

问题

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

谢谢。

【问题讨论】:

  • 您使用的是什么版本的 Spring Boot?您是否尝试使用server.forward-headers-strategy=NATIVE(添加了RemoteIpValve)而不是RemoteIpFilter
  • 嗯,我不知道。有趣的是,您甚至不需要设置该属性,因为 Spring Boot 会检测环境 (K8S) 并自动激活阀门。我遇到的问题是我的 Nginx 配置替换了 Forwarded-For 标头,而不是附加到它。请您发表您的评论作为答案还是我应该这样做?
  • 我无法完全解释RemoteIpFilter 是如何导致multipart/mixed 内容被拒绝的,所以最好提供答案。

标签: spring spring-boot tomcat


【解决方案1】:

感谢 Piotr P. Karwasz 的评论,我想出了如何做到这一点。这并不简单,因为它依赖于环境和其他组件(例如 LB、反向代理等)。但你会这样做:

  1. 如果您使用的是最新版本的 Spring Boot + Tomcat,请不要使用 RemoteIpFilter。如果需要,Spring Boot 会通过创建 RemoteIpValueTomcatWebServerFactoryCustomizer)来满足您的需求。
  2. 如果您在某个云平台上进行部署,您可能不需要做任何事情,因为如果 Spring 检测到该平台是云平台,它会自动创造价值。不需要明确地告诉它。 (查看 CloudPlatform.getActive(env) 返回的内容。)
  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 上做任何事情,因为我们将我们的应用程序部署到 K8S 上,Spring 在该 K8S 上创建了阀门。那为什么我没有得到正确的远程地址?原来我们的反向代理,Nginx有默认配置:

proxy_set_header X-Forwarded-For        $remote_addr;

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

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

  1. ServerProperties.Tomcat.Remoteip#remoteIpHeader 设置为X-Original-Forwarded-For,因为这是Nginx 在用$remote_addr 覆盖之前备份X-Forwarded-For 的原始值的地方。 (对我来说似乎是个 hack!)
  2. 将 Nginx 配置为根本不接触 X-Forwarded-For 标头。 (对我来说似乎是个 hack!)
  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:在新的花哨标题Forwarded 上还有一些其他注释应该可以解决整个混乱,但显然 Nginx 还没有完全支持它。

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

【讨论】:

    猜你喜欢
    • 2014-07-30
    • 1970-01-01
    • 2022-06-17
    • 2023-03-05
    • 2014-04-01
    • 2017-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多