首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 獲取客戶端真實 IP 地址的 6 種方法總結:應對代理、CDN、負載均衡

     編輯:PHP 相關知識

PHP 獲取真實 IP 完整方案:從基礎到企業級防偽造

在 CDN、負載均衡、反向代理、多層網關架構下,$_SERVER['REMOTE_ADDR'] 往往只能拿到代理服務器 IP。本文匯總 6 種獲取真實客戶端 IP 的方法,並提供**生產級封裝函數**,支持信任代理白名單、IPv6、私有 IP 過濾、HTTP 標準頭優先級判斷。

一、核心 HTTP 頭優先級標準

標準代理鏈路會依次添加:

  1. HTTP_CDN_SRC_IP:部分 CDN(如騰訊云、阿里云)自定義頭,最高可信。
  2. HTTP_CLIENT_IP:早期代理標準,易偽造。
  3. HTTP_X_FORWARDED_FOR (XFF):**標準多層代理頭**,格式 client, proxy1, proxy2,最左側為原始客戶端。
  4. REMOTE_ADDR:TCP 連接直接來源(最後一跳代理/客戶端)。

二、六種實現代碼對比

方法 1:標準優先級 + 私有 IP 過濾(推薦)

<?php
function getRealIp() {
    static $realIp = '';
    if ($realIp) return $realIp; // 緩存
    
    $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    
    // 1. CDN 專用頭(高可信)
    if (!empty($_SERVER['HTTP_CDN_SRC_IP'])) {
        $ip = $_SERVER['HTTP_CDN_SRC_IP'];
    }
    // 2. HTTP_CLIENT_IP
    elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }
    // 3. X-Forwarded-For:取最左側非私有 IP
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
        foreach ($ips as $xip) {
            if (filter_var($xip, FILTER_VALIDATE_IP) && !isPrivateIp($xip)) {
                $ip = $xip;
                break;
            }
        }
    }
    
    $realIp = $ip;
    return $realIp;
}

function isPrivateIp($ip) {
    // RFC 1918 私有地址段 + 環回 + 鏈路本地
    $ranges = [
        '10.0.0.0/8',
        '172.16.0.0/12',
        '192.168.0.0/16',
        '127.0.0.0/8',
        '169.254.0.0/16',
        '::1/128',      // IPv6 環回
        'fe80::/10',    // IPv6 鏈路本地
        'fc00::/7',     // IPv6 唯一本地
    ];
    foreach ($ranges as $range) {
        if (ipInCidr($ip, $range)) return true;
    }
    return false;
}

function ipInCidr($ip, $cidr) {
    list($rangeIp, $mask) = explode('/', $cidr);
    $mask = ~((1 << (32 - $mask)) - 1);
    return (ip2long($ip) & $mask) === (ip2long($rangeIp) & $mask);
}
?>

方法 2:兼容舊版 $HTTP_SERVER_VARS(PHP 4.1+)

<?php
function getIpLegacy() {
    $vars = $GLOBALS['HTTP_SERVER_VARS'] ?? $_SERVER;
    $keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
    foreach ($keys as $key) {
        if (!empty($vars[$key])) return $vars[$key];
    }
    return getenv('HTTP_X_FORWARDED_FOR') ?: getenv('HTTP_CLIENT_IP') ?: getenv('REMOTE_ADDR') ?: 'Unknown';
}
?>

方法 3:最簡單(僅直連環境)

<?php echo $_SERVER['REMOTE_ADDR']; ?>

方法 4:三元運算符簡寫

<?php
$userIp = ($_SERVER['HTTP_VIA'] ?? '') 
    ? ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'])
    : $_SERVER['REMOTE_ADDR'];
echo $userIp ?: $_SERVER['REMOTE_ADDR'];
?>

方法 5:函數式完整邏輯(含 eregi 已棄用,改用 preg_match)

<?php
function getRealIpV5() {
    $ip = false;
    if (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
        if ($ip) array_unshift($ips, $ip);
        foreach ($ips as $xip) {
            if (filter_var($xip, FILTER_VALIDATE_IP) && !preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
                return $xip;
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'];
}
?>

方法 6:getenv 兼容寫法

<?php
function getIpEnv() {
    $keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
    foreach ($keys as $key) {
        $val = getenv($key);
        if ($val) return $val;
    }
    return $GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR'] ?? 'Unknown';
}
?>

三、生產級最佳實踐:信任代理白名單 + Symfony Request

現代框架(Laravel、Symfony、Slim、Yii)均基于 Symfony\HttpFoundation\Request,內置**受信任代理**機制,徹底解決 IP 偽造問題。

composer require symfony/http-foundation

<?php
use Symfony\Component\HttpFoundation\Request;

Request::setTrustedProxies(
    ['10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12'], // 你的代理/CDN IP 段
    Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO
);

$request = Request::createFromGlobals();
$realIp = $request->getClientIp(); // 自動應用信任鏈邏輯
echo "真實 IP: $realIp";
?>

原理

  • 只信任來自白名單 IP 的 X-Forwarded-For 頭。
  • 自動解析鏈路,取最左側非受信代理 IP。
  • 同時修正 getScheme()、getHost()、getPort(),解決 HTTPS/端口判斷錯誤。

四、IPv6 支持補充

<?php
function getClientIpV6() {
    $headers = ['HTTP_CDN_SRC_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
    foreach ($headers as $h) {
        $val = $_SERVER[$h] ?? '';
        if ($val) {
            $ips = array_map('trim', explode(',', $val));
            foreach ($ips as $ip) {
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'];
}
?>

五、安全警示與檢查清單

  1. 不要直接信任任何 HTTP 頭:未經受信代理驗證的 XFF 完全可偽造。
  2. 配置 Nginx/Apache 真實 IP 模塊:
    # Nginx
    set_real_ip_from 10.0.0.0/8;
    set_real_ip_from 172.16.0.0/12;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;
  3. 應用層雙重校驗:框架 Request + 業務代碼雙重保險。
  4. 日志記錄完整鏈路:X-Forwarded-For、Via、Remote-Addr 入庫,便於事後審計。
  5. IPv6 就緒:數據庫欄位用 VARBINARY(16) 或 INET6 存儲,INET_ATON/INET_NTOA 不支持 IPv6。

**結論**:新項目**直接接入 Symfony Request**(或框架封裝),配置受信代理白名單,一行代碼解決所有 IP 獲取與偽造問題。老項目引入上述 getRealIp() 函數並部署 Nginx real_ip_module。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved