拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Nginx反向代理固定出口IP,彻底解决API白名单偶发鉴权失败

帮一个做餐饮SaaS的朋友对接外卖开放平台API前后折腾了快一周。业务代码写得很快签名算法也是按文档走的但在测试环境一调就报错返回的都是同一类鉴权失败。后来排查下来问题出在平台的IP白名单上——他那套测试环境是临时拉起来的Docker集群容器访问外网时源IP一会儿走EIP一会儿走NAT网关外卖平台那边看到的来源IP一直在变白名单当然形同虚设。最后就是用Nginx反向代理做统一出口把出网IP钉死在一个固定地址上问题才算彻底解决。这篇文章就是把这套方案的原理、配置和排查链路完整拆开来讲文章里有可直接抄的配置也有我踩过的坑建议从头看一遍因为很多反直觉的细节恰恰是故障源头。1. 外卖API对接卡在白名单平台验证的到底是什么1.1 白名单失效的典型现象先描述一下当时的现象看有没有对号入座的。配置好Webhook和接口凭证之后用Postman单发一个查询店铺营业状态的请求返回结果时好时坏。同一个请求体同一个签名连发十次七八次能通偶尔几次就丢回一个类似IP不可信或者来源IP不在白名单的异常而且没有太详细的错误码。这种偶发性失败最容易误导人。第一次遇到的时候我第一反应是签名时间戳的问题——外卖平台的API普遍会校验请求时间戳偏差超过几分钟直接拒绝。后来把服务器时间校准、把代码里的timestamp统一为毫秒级问题照旧才把目光转向网络层。如果你也遇到同一个客户端、同一个请求参数成功率不稳的情况别急着怀疑业务代码先拿实时出口IP打一发。我在现场用了一行命令for i in $(seq 1 20); do curl -s https://ifconfig.me; echo; done返回结果里出现了两个完全不同的IP一个属于云服务器公网IP段一个是NAT网关的地址段。到这一步基本实锤出口IP不固定平台白名单校验自然在概率性失败。1.2 IP白名单的校验层次要理解这套方案首先要清楚平台侧的IP校验到底发生在哪一层。很多人误以为外卖开放平台校验的是HTTP头里的X-Forwarded-For或X-Real-IP于是拼命在Nginx往上游加头结果毫无用处。实际上绝大多数API平台的IP白名单校验发生在TCP连接层也就是三次握手时看到的对端IP地址。平台Web层拿到请求之后从socket中取到remote_addr和白名单列表比对匹配才放行。HTTP头里你填什么IP平台基本不关心——因为这层信息客户端完全可以伪造平台可不会拿它当安全凭证。这样理解就清晰了平台看到的是你到平台网关这一跳的源地址。如果请求路径是业务容器 - NAT网关 - 公网 - 平台网关那么平台看到的源IP不是业务容器也不是业务服务器而是NAT网关的公网出口IP。如果NAT网关端口或公网地址不固定比如云厂商的默认NAT实例、共享带宽池那么平台每次看到的就是漂移的IP。1.3 为什么出口IP会漂移出口IP漂移常见的来源有这么几类我自己的项目里基本全遇到过容器网络默认走NATNAT网关本身有多个弹性公网IP未绑定固定IP源地址在不同连接里随机挑一个。服务器有多个默认路由多网卡、多线机房路由表在不同时刻选了不同的下一跳源IP跟随变化。云厂商的SNAT规则没做固定映射默认用端口或会话哈希机制源IP随后端节点变化。服务器在某些时段被切到了备用出口比如双线机房故障切换切换前和切换后源IP不同。通俗一点说平台的IP白名单就像小区门禁只认你这张脸IP。你每天从不同门进出多个出口IP保安当然觉得你眼生偶尔就不让进。2. 反代方案不是银弹先确定网络拓扑再动手2.1 先画出你的出口拓扑在配置Nginx之前一定要先把自己的出网路径画清楚否则就是瞎配。我一般会先问这几个问题业务服务器到外卖平台的流量是直连公网还是经过NAT网关服务器有几张网卡、几条默认路由可以用ip route查看。出口公网IP是固定的弹性IP还是临时分配的业务是否运行在容器或K8s集群内Pod访问外网时走了什么CNI的NAT规则把这些问题搞清楚你才知道Nginx反代要放在哪一层才能真正固定住平台看到的源IP。2.2 几种常见方案的取舍我在实际项目里评估过四类方案各自适用场景差别挺大的方案适用场景优势劣势单台Nginx反向代理服务器数量少、出口逻辑简单改造小、成本低、容易排查存在单点故障NAT网关绑定固定EIP云上VPC环境有多个后端节点无需改业务代码节点级透明需要云厂商功能支持可能产生流量费用HAProxy keepalived对可用性要求高不能接受单点高可用、故障切换快配置、运维复杂K8s Egress Gateway业务已经容器化且规模较大集中管控Pod出口引入额外组件学习成本高当时朋友那个项目还在创业早期服务器一共就三台一台跑数据库两台跑业务容器。外卖API调用量也不算大主要就是拉订单、回传状态。这种情况下上Egress Gateway明显过重NAT网关方案需要额外配置且依赖云厂商限制较多于是他选择了最低成本的单台Nginx反代。2.3 我为什么最终选择Nginx反代不是Nginx有多炫技核心原因是它符合最小改动的原则。业务代码里把外卖API的请求地址统一指到内网这台Nginx反代上反代再去请求外卖平台。平台的白名单只需加这一台反代服务器的公网IP简单清晰。这个方案的实质是把出口IP从多个不可控收敛成一个可控。Nginx反代就像一个固定的门卫业务侧所有请求都从这扇门出去平台侧看到的永远是这个门卫的IP。需要注意如果你的业务服务器本身有多个出口且绕过Nginx直连了外卖平台那白名单还是会被绕过。所以上线反代的同时必须用防火墙规则掐断业务服务器直达外卖平台的通路强制流量走Nginx。这一步很多人漏了导致白名单时好时坏。3. Nginx反向代理与出口IP固定的落地配置3.1 最小可用配置先给一份可以在业务环境直接套用的Nginx配置。以下配置把Nginx监听在8080端口业务侧调用时把外卖API的域名替换为http://nginx-proxy-ip:8080Nginx会把请求原样转发到外卖开放平台的HTTPS接口。worker_processes auto; events { worker_connections 10240; } http { # 显式指定DNS缩短外卖平台域名解析缓存的TTL避免IP漂移后Nginx长时间缓存旧地址 resolver 10.0.0.2 valid30s ipv6off; server { listen 0.0.0.0:8080; # 这里用变量承载后端地址目的是让Nginx每次请求都通过resolver动态解析 # 而不是在reload时才固定解析一次 set $backend_host openapi.xxx-waimai.example.com; set $backend_url https://$backend_host; location / { proxy_pass $backend_url; proxy_http_version 1.1; # 保留原始Host头防止外卖平台按域名做路由时出错 proxy_set_header Host $backend_host; proxy_set_header Connection ; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # HTTPS相关配置确保Nginx与外卖平台之间TLS握手携带正确的SNI proxy_ssl_server_name on; proxy_ssl_name $backend_host; proxy_connect_timeout 5s; proxy_read_timeout 15s; proxy_send_timeout 15s; } } }这份配置里有几个点必须重点说明不然容易踩坑。第一proxy_pass后面的URL是否带路径、是否用变量行为完全不同。这里用变量是为了让Nginx在每次请求时都动态DNS解析。如果直接写死域名Nginx会在启动或reload时解析一次最长可能缓存到下次reload一旦外卖平台更改解析或者解析到多个IP你就会在Nginx日志里看到连接被定向到已失效的IP。第二用了变量后proxy_pass里必须带上协议类型。很多教程里写proxy_pass http://backend;在location里配合upstream块使用那是另一套写法。用变量动态转发时URL必须以http://或https://开头。我之前犯过漏掉协议导致Nginx启动报错的问题。第三长连接的设置。proxy_http_version 1.1和Connection 是为了让Nginx和上游外卖平台之间复用TCP连接。好处是减少TLS握手开销坏处是如果Nginx与平台之间维护着长连接且平台的负载均衡器会按连接散列到后端节点那么你所有请求其实都落在这条固定连接上。这个特性对IP白名单没有影响但对平台侧的限流策略可能有影响后面会展开讲。3.2 用iptables把出网IP钉死Nginx配置好之后还差一步确保这台Nginx服务器访问外卖平台时使用的源IP是固定且符合白名单的那个公网IP。如果服务器本身只有一个公网IP这一步天然满足什么都不用做。但很多服务器是多网卡或绑定多个弹性公网IP的云主机系统默认选择的源IP不一定是网卡主IP这时就需要强制SNAT规则。以一台绑定固定EIP1.2.3.4、内网网卡eth0的云主机为例可以用下面的iptables规则确保所有发往外卖平台网段的流量都从1.2.3.4出去# 查看当前nat表规则避免重复添加 iptables -t nat -L POSTROUTING -n -v # 为外卖平台API网段添加强制SNAT规则 # 这里以203.0.113.0/24代替实际的外卖平台网段实际请按平台的IP段调整 iptables -t nat -A POSTROUTING -d 203.0.113.0/24 -o eth0 -j SNAT --to-source 1.2.3.4这条规则的意图是凡是目的地址属于外卖平台API网段、且从eth0网卡出去的包源IP统一改写成1.2.3.4。这样无论路由表怎么选平台看到的源IP都不会变。如果你的外卖平台API域名解析到多个IP段建议先把域名解析到的IP段全部查出来然后给每个IP段配置一条类似的SNAT规则。可以用dig short查看dig short openapi.xxx-waimai.example.com还需要注意云厂商通常有自己的安全组和防火墙iptables的SNAT规则只对本机生效不负责拦截入站流量。安全组如果限制了出站源IP那SNAT也会不生效需要同步检查安全组规则。3.3 HTTPS证书校验与SNI的坑外卖平台的接口几乎都是HTTPSNginx反代去请求上游的时候自身扮演的是客户端角色需要完成TLS握手。默认情况下Nginx会校验上游证书的有效性如果你的反代服务器没有安装对应的根证书或者上游证书链不完整TLS握手会直接失败请求到不了平台。我在测试时遇到过一个隐蔽问题平台网关做了多域名复用证书TLS握手时依赖SNI来返回正确的证书。Nginx转发HTTPS请求时默认不携带SNI导致平台侧无法按域名分发证书握手直接被重置。解决方式就是配置里那两行proxy_ssl_server_name on; proxy_ssl_name $backend_host;这种问题最大的困扰是一般不会报证书错误而是超时或连接被重置。排查时在Nginx日志里看到upstream prematurely closed connection就要优先怀疑TLS/SNI问题。3.4 多实例场景的域名解析与长连接策略如果你的业务是多个后端实例同时调用外卖平台且都需要走Nginx反代那么Nginx上游连接的情况会更复杂。多个业务实例和Nginx之间可以采用长连接池但Nginx到外卖平台之间建议还是保持有限长度的连接。连接数太多会导致平台侧认为你在高频刷新连接数太少又可能导致头部阻塞。我的习惯是把worker_connections调大但Nginx到外卖平台的连接数通过proxy_http_version 1.1Connection 复用。如果发现平台侧对单IP并发连接数有限制可以在Nginx里做limit_conn限制同一时刻到某个上游的连接数避免触发平台的封禁策略。4. 上线前验证出口IP与定位问题的完整链路4.1 验证出口IP的两类手段配置完成不等于一切正常必须验证从Nginx反代访问外卖平台时平台实际看到的IP就是白名单IP。验证方法分两类一类是验证本机出网IP另一类是验证平台视角的来源IP。很多教程只讲了前者后者才是关键。本机出网IP验证# 在Nginx服务器上执行看公网视角的出口IP curl -s https://ifconfig.me curl -s https://ipinfo.io/ip确认返回的IP等于你加白名单的那个IP。如果不等检查前面提到的SNAT规则以及路由表有没有生效。平台视角的来源IP验证最直接的方式是到外卖开放平台的开发者后台看调用日志或接口监控里记录的来源IP。大多数开放平台会展示最近调用的来源IP、响应码、耗时。加白之后连续发几个请求再去平台后台看到IP和你预期的一致才算真正闭环。如果平台后台看不到来源IP还可以在Nginx反代服务器上用tcpdump抓包看发送到外卖平台网段的包源地址和目的地址是否符合预期# 抓取发往外卖平台API网段的流量可按实际网段调整 tcpdump -i eth0 -nn host 203.0.113.10 and tcp port 443 -c 20在抓包结果里每一行都会显示源IP.端口 目的IP.443源IP就是平台侧会看到的地址。4.2 白名单生效确认白名单加白之后外卖平台通常不会立刻生效尤其是一些老牌平台配置变更可能需要几十秒甚至几分钟才会同步到边缘节点。所以刚加完白名单时请求失败先别慌等一两分钟再重试。我习惯的做法是写一个简单的循环脚本每10秒打一个请求把返回码和时间戳记录下来直到连续成功三次为止for i in $(seq 1 30); do code$(curl -s -o /dev/null -w %{http_code} https://openapi.xxx-waimai.example.com/some/health/endpoint) echo $(date %H:%M:%S) $code sleep 10 done如果循环跑到最后几次还在返回鉴权失败基本可以判定不是白名单延迟问题而是出口IP仍然没有固定回到第4.1节重新排查。4.3 常见失败现象定位表实际运维中我整理过一张排查表出现以下现象时可以按表定位现象可能原因排查重点偶发性鉴权失败出口IP未固定检查Nginx所在服务器多路由、多网卡、SNAT配置完全鉴权失败IP白名单未加对确认平台后台填的是Nginx出网IP不是业务服务器IPHTTPS连接被重置SNI未正确携带配置proxy_ssl_server_name on请求超时DNS解析缓存到失效IP使用Nginx resolver 变量动态解析成功率高但偶发5xx平台侧连接数限制检查Nginx到上游的keepalive复用情况只有部分接口失败接口域名不同、白名单未覆盖该域名出口逐个域名确认出口IP这张表不是我拍脑袋写的是那次项目里真实遇到过的几个问题的汇总按顺序排查效率会高很多。5. 后续运维中的边界情况与经验补全5.1 平台API域名解析到多个IP的影响外卖开放平台的API域名DNS解析结果往往不只是一个IP。我在配置早期就发现dig short返回了一串地址分别对应平台的不同接入点。这个现象对IP白名单没有本质影响——因为白名单验证的是TCP源IP不是目的IP。你从固定出口IP发起的连接无论打到平台的哪个后端节点源IP都是不变的鉴权都能通过。但当平台侧有针对来源IP目的IP的限流策略时多个目的IP之间流量权重不均可能导致某个特定后端节点上你的来源IP请求偏多触发单点限流。这种情况一般表现为整体成功率正常但某个时间段内偶发超时或429。排查方法很直接在Nginx日志里统计每个上游目的IP的请求量分布发现严重不均时考虑在DNS解析层做加权或者在外卖平台把测试环境切到独立域名。5.2 不只靠IP白名单还需要关注UA、AppKey和签名IP白名单只是外卖平台安全策略的第一道门。平台侧通常还会校验请求头里的User-Agent是否符合开放平台指定的格式AppKey与Secret签名是否匹配请求时间戳与服务器时间差是否在允许范围内Webhook回调是否验签。如果你通过Nginx反代转发请求而Nginx在转发时修改了Host头或User-Agent平台侧可能在白名单之外的其他维度把你拦下来。所以proxy_set_header里尽量保留原始信息特别是Host头。虽然外卖平台的API一般通过域名而非Host来路由但某些内部网关仍然会用Host做租户识别改了Host轻则路由错乱重则报签名和租户不匹配。5.3 从单点反代到高可用的演进思路单台Nginx反代最大隐患就是单点。如果机器故障或网络不通所有外卖API调用全部中断。对还在起步阶段的系统来说可以接受但业务量上来后就要考虑高可用。高可用方案通常引入两台Nginx反代前面加一个负载均衡设备或云厂商SLB两台Nginx的公网出口IP固定为同一个EIP通过主备切换外卖平台白名单只需要配一个IP。如果两台Nginx的出口IP不同就要把两个IP同时加白并在故障切换时保证平台白名单覆盖到备用IP。用keepalived做故障切换时需要把虚拟IP绑定在两台机器之间浮动。主节点故障后备节点接管VIP出网流量继续从VIP绑定的EIP出去。这个方案在云主机上一般需要关闭源/目的检查否则VIP绑定的EIP不生效。这是我后来在另一个项目里踩过的坑这里提前帮你们避掉了。另外如果你们公司内部有统一的NAT网关或API网关完全可以把外卖平台的调用统一收敛到公司网关层内部各业务系统不再直接对接外卖API而是对接公司网关。这样IP白名单只配置在网关的出口后续新增业务实例不会影响白名单配置管理和治理也轻松很多。我在做后续扩展时就倾向于这个方向因为业务线多了以后每加一个服务就去开放平台加一次白名单显然不可持续。回到开头那个项目方案落地之后外卖平台的调用再也没有出现过偶发鉴权失败。整个排查和改造过程最耗时间的其实不是Nginx配置而是定位出口IP不固定这个根因。如果你现在也被IP白名单的偶发失败折磨建议不要急着在业务代码里到处加日志先花10分钟把出口IP的稳定性验证一遍大概率能省下一天的排查时间。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门