网站一旦套上CDN,后端日志里原本清晰的客户端IP就全变成了CDN边缘节点的地址。同一个用户反复刷接口,日志里出现的却是几十个不同的IP;反过来,成千上万个用户访问,日志里可能只有三五个IP在打转。这时候要想做风控、封禁或者访问统计,就必须从请求头里把真实IP挖出来。而最常被拿来用的两个头就是X-Forwarded-For和True-Client-IP,它们看似干同一件事,实际的可信度和取值逻辑差别很大,用错了很容易把伪造的IP当成真实用户。

一、X-Forwarded-For的传递机制与伪造风险
X-Forwarded-For(简称XFF)是最通用的代理链传递头。它的规则是:每经过一层代理,代理节点就会把自己看到的上一跳地址追加到这个头的末尾,用逗号分隔。举个完整的链路例子:客户端IP是1.2.3.4,请求先经过一个伪造头的第一层代理5.6.7.8,再经过CDN节点9.10.11.12,最后到达源站。此时源站收到的XFF值可能是:
X-Forwarded-For: 1.2.3.4, 5.6.7.8
注意最后一个代理(CDN节点)自己的IP 9.10.11.12不会出现在XFF里,而是记录在Remote Address或另一头X-Real-IP中。XFF里第一个IP通常是客户端声称的地址,最后追加的IP是离自己最近的上一层代理看到的来源地址。因此有一条重要的信任规则:从右往左数,第一个不被信任的IP才是可信的客户端地址。右边的IP由可信代理追加,可信;越往左越容易被伪造。
XFF最大的坑在于它是一个普通请求头,客户端完全可以自己构造。如果源站不加过滤直接取第一个IP,攻击者只要发一个带X-Forwarded-For: 8.8.8.8的请求,你的封禁名单就永远封不到他。正确的姿势是明确自己有几层可信代理,比如只有一层CDN,那就取倒数第一个IP(即CDN追加的那个)。
二、True-Client-IP的特点与适用场景
True-Client-IP是部分CDN厂商(如Akamai、Cloudflare Enterprise等)提供的专用头。它和XFF的核心区别在于写入策略:CDN节点会强制覆盖这个头,把它改写成自己检测到的客户端真实IP,而不是追加。也就是说,即使客户端伪造了True-Client-IP,到达源站时这个值也已经被CDN重写,伪造内容不会透传。
但这个头也有局限:第一,它不是通用标准,不同厂商支持情况不一,甚至有的厂商默认不开启,需要在控制台配置;第二,如果你的CDN不支持覆盖写入,这个头反而和XFF一样可伪造。所以使用前必须确认厂商文档中明确说明该头由CDN强制写入。判断方法很简单:自己用curl发一个带假True-Client-IP的请求,看源站收到的是假值还是真实出口IP。
# 测试该头是否可被伪造 curl -H "True-Client-IP: 1.1.1.1" -H "X-Forwarded-For: 2.2.2.2" https://www.ipipp.com/echo-ip
如果回显1.1.1.1,说明CDN没有覆盖这个头,不能信任;如果回显你的真实公网IP,则可以放心使用。
三、Nginx与后端代码的实战配置
在Nginx侧,最优雅的方案是使用ngx_http_realip_module模块。它会在请求处理早期就把remote_addr替换为真实IP,后续的日志、限流、封禁模块全都自动生效,不需要每个应用单独适配。
# 仅有一层CDN的情况,取XFF最后一个IP set_real_ip_from 0.0.0.0/0; # 实际应填CDN节点网段,不要全放通 real_ip_header X-Forwarded-For; real_ip_recursive on; # CDN支持True-Client-IP时优先使用 # real_ip_header True-Client-IP;
这里有两个关键点。set_real_ip_from一定要写CDN官方公布的回源网段,写0.0.0.0/0等于把伪造的大门敞开,任何人都能源站直连冒充CDN。real_ip_recursive开启后,Nginx会从右往左跳过所有可信IP,停在第一个不可信地址上,这正是我们想要的值。
应用层解析可以封装一个通用函数,以Java为例:
public static String getClientIp(HttpServletRequest request) {
// 优先使用CDN覆盖写入的头(需确认厂商支持)
String ip = request.getHeader("True-Client-IP");
if (isValid(ip)) {
return ip;
}
String xff = request.getHeader("X-Forwarded-For");
if (xff != null && !xff.isEmpty()) {
// 只有一层CDN时,取最后一个IP(由CDN追加,可信)
String[] parts = xff.split(",");
return parts[parts.length - 1].trim();
}
return request.getRemoteAddr();
}多级代理场景下不能简单取最后一位,需要维护一份可信代理网段列表,从右往左逐个匹配,跳过可信段后停住。此外还要做好格式校验:IPv4、IPv6格式验证,以及识别unknown、空串等异常值,避免脏数据进库。
四、验证方案是否生效与常见误区
配置完成后务必验证。用手机流量(不要连WiFi)访问站点,对比后台日志中记录的IP与手机查询到的公网IP是否一致。再补一组伪造测试:带上假的XFF头请求,确认日志记录的仍是真实IP而非伪造值。
常见的误区有三个:一是无脑取XFF第一个IP,前面已经分析过这等于信任客户端自报家门;二是set_real_ip_from范围放得过大,或者干脆忘了限制,源站IP一旦泄露就会被直连伪造;三是源站安全组没有只对CDN回源网段放行。最后这一点容易被忽略——如果源站80端口对全公网开放,攻击者完全可以绕过CDN直连源站,此时所有基于请求头的逻辑都形同虚设。正确的做法是源站防火墙只允许CDN回源网段访问,形成闭环。
总结一下选型建议:CDN支持True-Client-IP且已开启覆盖写入,优先用它,实现简单且天然防伪造;否则用XFF配合可信代理网段从右往左取值。无论哪种方案,限制回源来源加日志验证这两步都不能省,安全链条上少了任何一环,真实IP都可能变成攻击者手里的道具。
X-Forwarded-ForTrue-Client-IPCDN真实IP修改时间:2026-09-04 06:30:38