如何在 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);
问题
- 使用
RemoteIpFilter
是个好主意吗?
- 是否有更好的方法来解决上述问题?
RemoteIpFilter
的最佳订单号是多少?
谢谢。
多亏了 Piotr P. Karwasz 的评论,我才知道如何做到这一点。这并不简单,因为它取决于环境和其他组件(例如 LB、反向代理等)。但这就是您的处理方式:
- 如果您使用的是最新版本的 Spring Boot + Tomcat,请不要使用
RemoteIpFilter
。 Spring Boot 通过创建一个 RemoteIpValue
(在 TomcatWebServerFactoryCustomizer
中)来满足您的需求。
- 如果您在某个云平台上进行部署,您很可能不需要执行任何操作,因为如果 Spring 检测到该平台为云平台,它会自动创建值。无需明确告诉它。 (看什么
CloudPlatform.getActive(env)
returns。)
- 如果您不在云平台上部署,可能需要至少设置以下属性之一:
ServerProperties#forwardHeadersStrategy = NATIVE
(正如 Piotr 所建议的)
ServerProperties.Tomcat.Remoteip#protocolHeader
(任何内容,例如 X-Forwarded-Proto
)
ServerProperties.Tomcat.Remoteip#remoteIpHeader
(任何内容,例如 X-Forwarded-For
)
- 要查看它是否真的有效,请为
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.
所以你有两个选择来解决这个问题:
- 将
ServerProperties.Tomcat.Remoteip#remoteIpHeader
设置为 X-Original-Forwarded-For
因为这是 Nginx 在用 $remote_addr
覆盖之前备份 X-Forwarded-For
的原始值的地方。 (对我来说似乎是个黑客!)
- 将 Nginx 配置为完全不接触
X-Forwarded-For
header。 (对我来说似乎是个黑客!)
- 配置 Nginx 以附加
X-Forwarded-For
而不是覆盖它。
要配置 Nginx 追加,您有两个选择:
- 处理类似这样的事情
proxy_set_header X-Forwarded-For "$remote_addr, $server_addr"
并将 Nginx 配置为追加而不是覆盖。 (有关如何在 K8S 上执行此操作,请参阅 this。虽然我无法让它工作;Nginx 抱怨 proxy-set-header
或类似内容的参数数量。我也不确定它是否是防弹解决方案)
- 配置您的 Helm 图表安装值:
config:
"use-forwarded-headers": "true" # not true
"compute-full-forwarded-for": "true" # not true
感谢 Piotr 的提示 :)
PS:还有一些关于新花式 header Forwarded
的其他注意事项应该可以解决整个混乱,但显然 Nginx 还没有完全支持它。
PSS:我没有花时间弄清楚为什么抛出异常或如何防止它。这可能是另一天:)
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);
问题
- 使用
RemoteIpFilter
是个好主意吗? - 是否有更好的方法来解决上述问题?
RemoteIpFilter
的最佳订单号是多少?
谢谢。
多亏了 Piotr P. Karwasz 的评论,我才知道如何做到这一点。这并不简单,因为它取决于环境和其他组件(例如 LB、反向代理等)。但这就是您的处理方式:
- 如果您使用的是最新版本的 Spring Boot + Tomcat,请不要使用
RemoteIpFilter
。 Spring Boot 通过创建一个RemoteIpValue
(在TomcatWebServerFactoryCustomizer
中)来满足您的需求。 - 如果您在某个云平台上进行部署,您很可能不需要执行任何操作,因为如果 Spring 检测到该平台为云平台,它会自动创建值。无需明确告诉它。 (看什么
CloudPlatform.getActive(env)
returns。) - 如果您不在云平台上部署,可能需要至少设置以下属性之一:
ServerProperties#forwardHeadersStrategy = NATIVE
(正如 Piotr 所建议的)ServerProperties.Tomcat.Remoteip#protocolHeader
(任何内容,例如X-Forwarded-Proto
)ServerProperties.Tomcat.Remoteip#remoteIpHeader
(任何内容,例如X-Forwarded-For
)
- 要查看它是否真的有效,请为
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.
所以你有两个选择来解决这个问题:
- 将
ServerProperties.Tomcat.Remoteip#remoteIpHeader
设置为X-Original-Forwarded-For
因为这是 Nginx 在用$remote_addr
覆盖之前备份X-Forwarded-For
的原始值的地方。 (对我来说似乎是个黑客!) - 将 Nginx 配置为完全不接触
X-Forwarded-For
header。 (对我来说似乎是个黑客!) - 配置 Nginx 以附加
X-Forwarded-For
而不是覆盖它。
要配置 Nginx 追加,您有两个选择:
- 处理类似这样的事情
proxy_set_header X-Forwarded-For "$remote_addr, $server_addr"
并将 Nginx 配置为追加而不是覆盖。 (有关如何在 K8S 上执行此操作,请参阅 this。虽然我无法让它工作;Nginx 抱怨proxy-set-header
或类似内容的参数数量。我也不确定它是否是防弹解决方案) - 配置您的 Helm 图表安装值:
config:
"use-forwarded-headers": "true" # not true
"compute-full-forwarded-for": "true" # not true
感谢 Piotr 的提示 :)
PS:还有一些关于新花式 header Forwarded
的其他注意事项应该可以解决整个混乱,但显然 Nginx 还没有完全支持它。
PSS:我没有花时间弄清楚为什么抛出异常或如何防止它。这可能是另一天:)