一个内网 IP,让我的登录限流彻底失效了
一次由部署架构与 IP 获取逻辑耦合引发的限流故障,从抓包、止血到根因分析,以及为什么不能直接读取 X-Forwarded-For、为什么要从右往左解析代理链。问题本身不大,但这类环境变化导致的"代码没改却出故障"的场景,很值得复盘。
前几天给登录接口加了一套基于 IP 的限流策略,上线后一切正常。直到下午同事突然来找我:系统提示"失败次数过多",登不进去了。
更尴尬的是,这位同事当时正在测试注册功能——我们的注册流程会先调用登录接口判断用户是否已存在。他排查了半天自己的代码,最后才发现是登录接口在报错。
就在大家讨论的时候,有人提了一句:"今天有人上线了客户端 IP 获取逻辑的优化。"
我听到这句话的瞬间,心里咯噔了一下。
登录限流正是基于客户端 IP 统计的。如果 IP 获取出了问题,大量用户可能会被塞进同一个限流桶里。
排查方向立刻转向了 IP 获取逻辑。
抓包确认了前端只发了一次登录请求,没有重复点击,没有轮询调用。限流参数也正常,用户维度和 IP 维度的阈值都合理,不可能一次请求就触发限流。
问题出在统计数据上。
先止血,再查根因。清理了 Redis 中已累计的失败次数,把限流参数改成支持配置中心动态调整,针对部分登录场景加了降级处理。业务恢复后继续排查。
然后我在 Redis 里看到了一个奇怪的现象:大量失败请求的客户端 IP 都是同一个地址——172.16.x.x。
这是个 Docker 内网地址。
更奇怪的是,并不是所有请求都拿到这个地址,只有一部分。如果 IP 获取逻辑有 Bug,应该所有请求都有问题才对。
我看了下部署结构,Auth 服务是多实例部署,其中部分实例和 Nginx 跑在同一台宿主机上,Nginx 跑在 Docker Bridge 网络里。
问题就出在这部分实例上。
正常的请求路径是:用户 → Nginx(Docker)→ Auth 实例。正常情况下,request.getRemoteAddr() 拿到的是可信代理地址,程序继续解析 X-Forwarded-For,最终得到真实客户端 IP。
但同机部署的实例不一样。由于 Docker Bridge 网络的通信方式,应用拿到的 TCP 对端地址变成了 Docker 容器自己的地址,比如 172.16.x.x。而这个地址没有配到可信代理白名单里,程序认为当前请求不是来自可信代理,直接返回了 request.getRemoteAddr()。
所有经过这个实例的请求,共享了同一个"客户端 IP"。IP 限流瞬间被触发。哪些用户受影响,完全看负载均衡把请求分配到哪个实例。
为什么不能直接读 X-Forwarded-For?很多人会问这个问题。Nginx 明明已经把真实 IP 写进去了,直接取不就行了?
因为客户端可以伪造。
GET /login
X-Forwarded-For: 8.8.8.8
如果程序无条件信任这个 Header,攻击者就能绕过 IP 限流、黑名单、风控策略,甚至污染日志数据。安全获取客户端 IP 的核心原则是:只有 TCP 连接本身来自可信代理时,才解析 X-Forwarded-For。否则一律用 request.getRemoteAddr()。
那为什么从右往左解析?假设 Header 是:
X-Forwarded-For: 223.5.5.5,10.0.0.1,172.16.x.x
客户端真实 IP 是 223.5.5.5,经过代理1(10.0.0.1),再到 Nginx(172.16.x.x)。很多人直接取最左边,这其实不安全,因为客户端完全可以伪造第一个值。正确的做法是从右往左遍历,跳过所有可信代理,遇到的第一个非可信地址才是真正的客户端 IP。
这次故障里,异常请求走到应用时,remoteAddr 是 172.16.x.x,不在可信代理列表里,程序直接返回了这个地址,大量请求共享同一个 IP,限流被触发。整个流程符合设计预期,问题是 Docker 网络中的代理地址没有被纳入可信代理范围。
修复方案很简单,把这个地址加进去就行:
trusted:
proxies: 172.16.x.x如果存在多个代理或者地址会变化,建议支持 CIDR 网段配置:
trusted:
proxies:
- 172.16.0.0/16另外还加了几个监控:同一 IP 短时间内大量触发限流、客户端来源出现大量内网地址、Trusted Proxy 配置变更、Docker 网络异常变化——这些以后能帮我更早发现问题。
这次故障看起来是一次登录限流异常,实际上暴露的是 IP 获取逻辑和部署架构之间的耦合问题。
很多人以为 request.getRemoteAddr() 返回的就是用户真实 IP,其实它返回的只是当前 TCP 连接对端的地址。这个地址可能是用户、Nginx、Docker Bridge、四层负载均衡、七层代理——取决于部署架构。
真正可靠的客户端 IP 获取方案,至少要做到这几件事:不直接信任 X-Forwarded-For、建立可信代理机制、从右向左解析代理链、支持 CIDR 网段配置、对异常内网来源建立监控告警。
很多线上故障不是代码写错了,而是代码运行的环境变了。同一段 request.getRemoteAddr(),在不同网络拓扑下返回的结果完全不同——这是生产环境里最容易踩坑、也最容易被忽略的一类问题。