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

PHP 取得用戶真實 IP 地址:穿透代理、CDN 與負載平衡器的完整方案

     編輯:PHP 相關知識

前言

在生產環境中,用戶請求常經過 CDN(Cloudflare、Akamai)、反向代理(Nginx、HAProxy)、負載平衡器(ALB、SLB),導致 $_SERVER['REMOTE_ADDR'] 只能取得代理伺服器 IP。本文提供穿透多層代理取得真實客戶端 IP 的健壯函數,並說明各 Header 含義與安全風險。

核心函數:real_ip()

<?php
/**
 * 取得用戶真實 IP 地址(支援 IPv4/IPv6)
 * 優先順序:HTTP_X_FORWARDED_FOR → HTTP_CLIENT_IP → REMOTE_ADDR
 * @return string IP 字串
 */
function real_ip(): string {
    static $realip = null;
    if ($realip !== null) return $realip;

    // 1. HTTP_X_FORWARDED_FOR:標準代理鏈,格式 "client, proxy1, proxy2"
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
        foreach ($ips as $ip) {
            // 過濾 unknown、私有 IP、保留位址
            if ($ip !== 'unknown' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                $realip = $ip;
                break;
            }
        }
    }

    // 2. HTTP_CLIENT_IP:某些代理使用(非標準)
    if ($realip === null && isset($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = trim($_SERVER['HTTP_CLIENT_IP']);
        if ($ip !== 'unknown' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
            $realip = $ip;
        }
    }

    // 3. REMOTE_ADDR:最後防線(直接連線或單層代理)
    if ($realip === null) {
        $realip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    // 4. 兼容舊版 getenv() 寫法(CGI 模式)
    if ($realip === '0.0.0.0') {
        $realip = getenv('HTTP_X_FORWARDED_FOR') 
               ?? getenv('HTTP_CLIENT_IP') 
               ?? getenv('REMOTE_ADDR') 
               ?? '0.0.0.0';
    }

    // 最終驗證格式
    if (!filter_var($realip, FILTER_VALIDATE_IP)) {
        $realip = '0.0.0.0';
    }

    return $realip;
}
?>

Header 來源與可信度分析

Header來源可信度說明
HTTP_X_FORWARDED_FOR標準代理鏈(RFC 7239)⭐⭐⭐⭐格式:client, proxy1, proxy2,第一個通常為真實客戶端,但可被偽造
HTTP_X_REAL_IPNginx proxy_set_header X-Real-IP⭐⭐⭐⭐⭐單一 IP,由可信任的反向代理設定,最可靠
HTTP_CLIENT_IP部分代理/負載平衡器⭐⭐非標準,較少見
HTTP_CF_CONNECTING_IPCloudflare⭐⭐⭐⭐⭐Cloudflare 專用,極度可信
HTTP_TRUE_CLIENT_IPAkamai、Cloudflare Enterprise⭐⭐⭐⭐⭐企業級 CDN 專用
REMOTE_ADDRPHP 直接取得的 TCP 連線 IP⭐⭐⭐若無代理則為真實 IP;有代理則為代理 IP

生產環境最佳實踐:信任代理清單

關鍵原則:只信任已知、可控的代理 IP 所傳遞的 Header。若直接信任 X-Forwarded-For,攻擊者可偽造 Header 繞過 IP 封鎖、竊取身分。

<?php
/**
 * 安全版:僅信任指定代理 IP 的 X-Forwarded-For
 */
function getRealIpSecure(): string {
    // 受信任的代理 IP 範圍(CIDR 格式)
    $trustedProxies = [
        '10.0.0.0/8',        // 內網
        '192.168.0.0/16',    // 內網
        '172.16.0.0/12',     // 內網
        '1.2.3.4/32',        // 特定 CDN 節點 IP
        // Cloudflare IP 範圍:https://www.cloudflare.com/ips/
        '173.245.48.0/20',
        '103.21.244.0/22',
        // ... 更多
    ];

    $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';

    // 1. 若請求直接來自客戶端(非受信代理),直接返回 REMOTE_ADDR
    if (!isIpInRanges($remoteAddr, $trustedProxies)) {
        return $remoteAddr;
    }

    // 2. 請求來自受信代理,解析 X-Forwarded-For 取第一個非私有 IP
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
        foreach ($ips as $ip) {
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
    }

    // 3. 回退
    return $remoteAddr;
}

function isIpInRanges(string $ip, array $cidrList): bool {
    if (!filter_var($ip, FILTER_VALIDATE_IP)) return false;
    foreach ($cidrList as $cidr) {
        if (ipInCidr($ip, $cidr)) return true;
    }
    return false;
}

function ipInCidr(string $ip, string $cidr): bool {
    list($subnet, $mask) = explode('/', $cidr);
    $mask = intval($mask);
    $ipBin = inet_pton($ip);
    $subnetBin = inet_pton($subnet);
    if ($ipBin === false || $subnetBin === false) return false; // IPv6 需額外處理
    $maskBin = str_repeat('f', $mask / 4) . str_repeat('0', (128 - $mask) / 4); // 簡化,僅示意
    // 實際建議使用 ip2long (IPv4) 或專用庫 (IPv6)
    return (ip2long($ip) & ~((1 << (32 - $mask)) - 1)) === ip2long($subnet); // IPv4 簡易版
}
?>

Nginx 設定範例:正確傳遞真實 IP

# nginx.conf
set_real_ip_from 10.0.0.0/8;        # 信任內網代理
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;     # 使用哪個 Header
real_ip_recursive on;               # 遞歸查找第一個非信任 IP

# 給 PHP 的變數
fastcgi_param HTTP_X_REAL_IP $realip_remote_addr;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;

Cloudflare 專用處理

<?php
function getCloudflareRealIp(): string {
    // Cloudflare 保證此 Header 真實性(需開啟 "Authenticated Origin Pulls")
    if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
        return $_SERVER['HTTP_CF_CONNECTING_IP'];
    }
    // 回退標準流程
    return real_ip();
}
?>

IPv6 支援補充

現代環境需支援 IPv6。filter_var($ip, FILTER_VALIDATE_IP) 同時支援 IPv4/IPv6。儲存資料庫欄位建議 VARBINARY(16) 或 VARCHAR(45)(IPv6 最大長度)。

<?php
function ipToBinary(string $ip): string {
    return inet_pton($ip); // IPv4 回傳 4 bytes, IPv6 回傳 16 bytes
}

function binaryToIp(string $bin): string {
    return inet_ntop($bin);
}

// MySQL 儲存範例
// INSERT INTO logs (ip_bin) VALUES (INET6_ATON('2001:db8::1'));
// SELECT INET6_NTOA(ip_bin) FROM logs;
?>

總結

  1. 永遠不要盲目信任 X-Forwarded-For,必須配合「信任代理 IP 清單」驗證來源。
  2. 優先使用 X-Real-IP、CF-Connecting-IP 等由可信基礎設施設定的 Header。
  3. 使用 filter_var() 驗證 IP 格式,過濾私有/保留位址(FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)。
  4. Nginx/Apache 層面設定 set_real_ip_from / RemoteIPTrustedProxy,讓 Web Server 幫你過濾,PHP 直接讀取乾淨的 REMOTE_ADDR 或自訂 Header。
  5. 記錄日誌時同時存入 REMOTE_ADDR(代理 IP)與解析出的真實 IP,利事後稽核。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved