DDoS防护客户端在实际部署中,最核心的问题就是如何在防护层后面准确提取用户的真实IP地址,并基于此建立一套可靠的信任IP白名单机制。很多企业部署了高防IP或CDN防护后,发现后端服务器看到的全部是代理节点IP,根本无法区分正常用户和攻击流量,导致误封、漏封频发。解决这个问题的关键在于三个层面:一是从HTTP请求头中正确解析真实IP字段(如X-Forwarded-For、X-Real-IP),二是在服务端配置信任代理链,只接受来自已知防护节点的转发请求,三是建立分级白名单策略,将核心业务IP、办公出口IP、合作伙伴IP纳入动态信任列表。下面我把这套方案从头到尾讲透。

一、为什么DDoS防护后真实IP会丢失

当流量经过DDoS清洗中心或高防CDN时,原始请求的源IP会被替换成防护节点的出口IP。这是防护机制的基本原理——所有流量先打到清洗中心,清洗后再转发到源站。问题在于,清洗中心通常会在HTTP请求头中附加一个字段来记录原始IP,最常见的就是X-Forwarded-For(XFF)和X-Real-IP。但这里有个巨大的坑:如果你的后端直接读取客户端IP而不去解析这些头部字段,你看到的永远是防护节点的IP,而不是用户的真实IP。更危险的是,XFF字段是可以被客户端自己伪造的,如果你不做任何校验就信任它,攻击者可以直接在请求头里写一个假IP绕过你的所有IP限制。

二、真实IP提取的具体方法和代码实现

提取真实IP的核心逻辑是:从请求头中按优先级读取X-Real-IP、X-Forwarded-For,同时必须验证该请求是否确实来自你信任的防护节点IP,否则直接丢弃或使用直接连接IP。下面给出几种主流后端的实现方式。

Nginx层面的配置方法,在nginx.conf的http或server块中加入:

# 定义信任的防护节点IP段
set_real_ip_from 103.28.xx.0/24;
set_real_ip_from 103.29.xx.0/24;
set_real_ip_from 120.xx.xx.xx;

# 指定从哪个头部取真实IP
real_ip_header X-Real-IP;
real_ip_recursive on;

PHP层面的提取函数:

function getRealIp($trustedProxies) {
    $headers = [
        'HTTP_X_REAL_IP',
        'HTTP_X_FORWARDED_FOR',
        'REMOTE_ADDR'
    ];
    foreach ($headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ipList = explode(',', $_SERVER[$header]);
            foreach ($ipList as $ip) {
                $ip = trim($ip);
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    // 只信任来自防护节点的IP
                    if (in_array($ip, $trustedProxies) || 
                        ipInRange($ip, $trustedProxies)) {
                        return $ip;
                    }
                }
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'];
}

function ipInRange($ip, $ranges) {
    foreach ($ranges as $range) {
        if (strpos($range, '/') !== false) {
            if (ip2long($ip) >= ip2long(explode('/', $range)[0]) &&
                ip2long($ip) <= ip2long(broadcastAddress($range))) {
                return true;
            }
        } elseif ($ip === $range) {
            return true;
        }
    }
    return false;
}

Python Flask/Django的实现思路类似,核心是在中间件层拦截请求,从request.headers中按优先级取IP,并校验来源。Java Spring Boot可以通过自定义Filter实现相同逻辑。关键点只有一个:必须先校验请求来源IP是否在你的防护节点白名单内,再去信任头部字段里的IP值。

三、信任IP白名单的分级策略

白名单不是简单地把所有IP往里一丢就完事了。实际运营中,我建议分成三级来管理。

第一级:防护节点白名单。这是最基础的一层,只有来自你部署的DDoS防护服务商节点的IP才被允许传递X-Forwarded-For或X-Real-IP字段。这一层的作用是防止外部伪造IP头。你需要向你的防护服务商索取所有出口IP段,配置到服务器的防火墙规则和应用层校验逻辑中。比如某高防服务商的清洗节点出口IP是103.28.54.0/24和103.29.54.0/24,那你就只信任这两个段。

第二级:业务信任IP白名单。这一层针对的是你自己的业务场景。比如你的管理后台只允许公司办公网出口访问,那就把公司的公网IP加进去;你的API接口只允许合作伙伴的服务器调用,那就把合作方的固定IP加进去。这一层通常配合应用层的访问控制一起使用,比如只有白名单内的IP才能访问/admin路径。

第三级:动态临时白名单。针对一些IP不固定但需要临时放行的场景,比如运维人员从家里紧急登录、临时调试接口等。可以设计一个带时效的白名单机制,通过短信验证或内部工单审批后自动添加,24小时后自动移除。这一层的核心是可控和可审计。

四、白名单管理的实操要点

很多人把白名单配置好就不管了,这是大忌。白名单需要持续维护,我总结几个必须做到的点。

第一,定期审计。每个月至少检查一次白名单列表,删除不再使用的IP,更新变动的IP段。特别是防护节点IP段,服务商可能会扩容或调整,你不更新就会导致正常流量被误拦截。

第二,最小权限原则。不要图省事把整个IP段都放进去,能精确到单个IP就不要用段。比如合作方只有一台服务器,你就只放那一个IP,不要放它整个机房的段。

第三,日志监控。所有命中白名单的请求和被白名单拦截的请求都要记录日志。一旦发现有异常IP频繁命中白名单,立刻排查是否被冒用或配置泄露。

第四,多层防护不要只依赖IP白名单。IP白名单是辅助手段,不是唯一防线。即使IP在白名单里,如果请求频率异常、载荷可疑,照样要触发限流或人机验证。真正的安全是纵深防御,IP白名单只是其中一环。

五、常见踩坑场景和解决方案

坑一:多层代理导致IP链混乱。有些企业同时用了CDN、WAF、高防IP,请求头里的XFF可能包含三四个IP。解决办法是只取最靠近防护节点的那个IP,也就是从右往左数第一个不在你信任列表中的IP,或者直接配置只信任最外层防护节点的XFF值。

坑二:内网用户IP被错误识别。如果你的服务器前面还有内网负载均衡,REMOTE_ADDR可能是内网IP。这时候必须确保内网LB也正确传递了X-Real-IP,并且在信任链中加入内网LB的IP段。

坑三:IPv6环境下的兼容问题。现在越来越多的用户使用IPv6访问,但很多防护节点和白名单配置只考虑了IPv4。务必确认你的防护方案和白名单策略同时支持IPv4和IPv6,否则IPv6用户会被全部拦截或无法正确识别真实IP。

坑四:防护服务商切换导致IP段全变。如果你更换了DDoS防护服务商,旧的IP白名单必须全部清除重新配置。建议在切换前做好流量灰度,新旧并行一段时间,确认无误后再彻底切换。

六、总结与建议

DDoS防护客户端的真实IP提取和信任IP白名单,本质上是一个"信任链"的问题。你需要建立从防护节点到应用层的完整信任传递机制,每一层都要验证上一层的身份,不能盲目信任任何一个头部字段。具体来说:先确认请求来自可信防护节点,再从头部提取真实IP,最后根据业务需求分级放入白名单。这套流程跑通之后,你的后端才能真正看到谁在访问你,才能做到精准防护而不是一刀切。

最后给一个实操建议:不要把白名单逻辑写死在代码里,最好做成可配置的动态规则,通过配置中心或数据库管理,这样运维调整时不需要重新发版。同时配合防火墙层面的IP限制做第一道防线,应用层白名单做第二道,业务逻辑层做第三道,三层叠加才是真正靠谱的方案。