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

PHP 獲取客戶端真實 IP 終極方案:代理穿透、IPv6 支持與安全防偽

     編輯:PHP 綜合知識

IP 獲取核心挑戰

在現代網絡架構中 (CDN、負載均衡、反向代理、K8s Service Mesh),$_SERVER['REMOTE_ADDR'] 往往只是直接上游代理的 IP。真實客戶端 IP 隱藏在各種 HTTP 頭部中,且面臨頭部偽造、多層代理鏈、IPv4/IPv6 混合等複雜問題。

HTTP 頭部來源優先級分析

頭部字段標準來源可信度典型值示例風險
HTTP_X_FORWARDED_FORRFC 7239⭐⭐⭐ (需驗證)client, proxy1, proxy2客戶端可偽造、多層代理追加
HTTP_X_REAL_IPNginx 慣例⭐⭐⭐⭐203.0.113.195僅單層 Nginx 代理可信
HTTP_CLIENT_IP非標準⭐⭐198.51.100.1極易偽造
HTTP_X_FORWARDED舊版代理⭐for=192.0.2.60;proto=http已棄用
HTTP_FORWARDEDRFC 7239⭐⭐⭐⭐⭐for="[2001:db8::1]";proto=https標準化、支持 IPv6、需解析
REMOTE_ADDRTCP 連接⭐⭐⭐⭐⭐ (基準)10.0.0.5不可偽造,但可能是內網代理 IP

企業級 IP 解析類

class ClientIpResolver {
    /** @var array 受信任代理 IP/CIDR 列表 (關鍵:防偽造) */
    protected $trustedProxies = [
        '10.0.0.0/8',      // 內網代理網段
        '172.16.0.0/12',
        '192.168.0.0/16',
        '127.0.0.1/32',
        // CDN 邊緣節點 IP 段 (需從云廠商獲取)
        // '1.2.3.0/24',    // 示例: Cloudflare 等
    ];
    
    /** @var array 頭部檢查優先級 */
    protected $headerPriority = [
        'HTTP_FORWARDED',      // RFC 7239 標準 (最高優先)
        'HTTP_X_FORWARDED_FOR', // 通用代理
        'HTTP_X_REAL_IP',       // Nginx
        'HTTP_CLIENT_IP',       // 兼容舊系統
    ];
    
    /** @var bool 是否嚴格模式 (僅信任受信代理鏈) */
    protected $strictMode = true;
    
    public function __construct(array $trustedProxies = [], bool $strictMode = true) {
        $this->trustedProxies = $trustedProxies ?: $this->trustedProxies;
        $this->strictMode = $strictMode;
    }
    
    /**
     * 獲取客戶端真實 IP
     * @return string IPv4/IPv6 地址或 'unknown'
     */
    public function getClientIp(): string {
        // 1. 獲取直接連接 IP (基準)
        $directIp = $this->getServerVar('REMOTE_ADDR');
        
        // 2. 非嚴格模式或無代理環境,直接返回
        if (!$this->strictMode || !$this->isTrustedProxy($directIp)) {
            return $this->getFirstValidIp($this->extractIpsFromHeaders());
        }
        
        // 3. 嚴格模式:驗證代理鏈可信
        $headerIps = $this->extractIpsFromHeaders();
        
        // 從右向左驗證代理鏈 (最右側是最早的代理)
        $validClientIp = 'unknown';
        foreach (array_reverse($headerIps) as $ip) {
            if ($this->isTrustedProxy($ip)) {
                continue; // 可信代理,繼續向左查找客戶端
            }
            if ($this->isValidClientIp($ip)) {
                $validClientIp = $ip;
                break; // 找到第一個非可信代理 IP,即為真實客戶端
            }
        }
        
        return $validClientIp !== 'unknown' ? $validClientIp : $directIp;
    }
    
    /** 從所有頭部提取 IP 列表 (保持順序: 客戶端 -> 代理1 -> 代理2...) */
    protected function extractIpsFromHeaders(): array {
        $allIps = [];
        foreach ($this->headerPriority as $header) {
            $value = $this->getServerVar($header);
            if (!$value) continue;
            
            if ($header === 'HTTP_FORWARDED') {
                $allIps = array_merge($allIps, $this->parseForwardedHeader($value));
            } else {
                $allIps = array_merge($allIps, $this->parseCommaSeparatedIps($value));
            }
        }
        return $allIps;
    }
    
    /** 解析 RFC 7239 Forwarded 頭部 */
    protected function parseForwardedHeader(string $value): array {
        $ips = [];
        // 格式: for=192.0.2.60;proto=http, for="[2001:db8::1]";by=proxy.example.com
        $parts = array_map('trim', explode(',', $value));
        foreach ($parts as $part) {
            // 提取 for= 參數
            if (preg_match('/for=([^;]+)/', $part, $matches)) {
                $ip = trim($matches[1], ' "[]'); // 去除引號和 IPv6 方括號
                if ($this->isValidIp($ip)) $ips[] = $ip;
            }
        }
        return $ips;
    }
    
    /** 解析逗號分隔的 IP 列表 */
    protected function parseCommaSeparatedIps(string $value): array {
        $ips = [];
        foreach (array_map('trim', explode(',', $value)) as $ip) {
            $ip = trim($ip);
            if ($this->isValidIp($ip)) $ips[] = $ip;
        }
        return $ips;
    }
    
    /** 獲取第一個有效客戶端 IP (非私有/保留地址) */
    protected function getFirstValidIp(array $ips): string {
        foreach ($ips as $ip) {
            if ($this->isValidClientIp($ip)) return $ip;
        }
        return $this->getServerVar('REMOTE_ADDR') ?: 'unknown';
    }
    
    /** 檢查 IP 是否在受信任代理列表中 */
    protected function isTrustedProxy(string $ip): bool {
        if (!$this->isValidIp($ip)) return false;
        foreach ($this->trustedProxies as $cidr) {
            if ($this->matchCidr($ip, $cidr)) return true;
        }
        return false;
    }
    
    /** 檢查是否為有效客戶端 IP (非私有/回環/保留) */
    protected function isValidClientIp(string $ip): bool {
        if (!$this->isValidIp($ip)) return false;
        // 排除私有/保留地址範圍
        $reserved = [
            '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',
            'fc00::/7', 'fe80::/10', '224.0.0.0/4', '240.0.0.0/4'
        ];
        foreach ($reserved as $cidr) {
            if ($this->matchCidr($ip, $cidr)) return false;
        }
        return true;
    }
    
    /** CIDR 匹配 (支持 IPv4/IPv6) */
    protected function matchCidr(string $ip, string $cidr): bool {
        if (strpos($cidr, '/') === false) return $ip === $cidr;
        
        [$subnet, $mask] = explode('/', $cidr);
        $mask = (int)$mask;
        
        // IPv6
        if (strpos($ip, ':') !== false) {
            if (!function_exists('inet_pton')) return false;
            $ipBin = inet_pton($ip);
            $subnetBin = inet_pton($subnet);
            if ($ipBin === false || $subnetBin === false) return false;
            $maskBin = str_repeat('f', $mask / 4) . str_repeat('0', 32 - $mask / 4);
            return substr(bin2hex($ipBin), 0, $mask / 4) === substr(bin2hex($subnetBin), 0, $mask / 4);
        }
        
        // IPv4
        $ipLong = ip2long($ip);
        $subnetLong = ip2long($subnet);
        if ($ipLong === false || $subnetLong === false) return false;
        $maskLong = -1 << (32 - $mask);
        return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
    }
    
    /** 基礎 IP 格式驗證 */
    protected function isValidIp(string $ip): bool {
        return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false
            || filter_var($ip, FILTER_VALIDATE_IP) !== false; // 允許私有 IP 進入後續邏輯判斷
    }
    
    protected function getServerVar(string $name): ?string {
        return $_SERVER[$name] ?? ($_ENV[$name] ?? null);
    }
}

使用示例與框架集成

1. 獨立使用

$resolver = new ClientIpResolver([
    '10.0.0.0/8',           // 內網
    '172.16.0.0/12',
    '192.168.0.0/16',
    '127.0.0.1/32',
    // 云廠商 CDN IP 段 (必須配置,否則嚴格模式下會被視為客戶端 IP)
    '103.21.244.0/22',      // Cloudflare 示例
    '103.22.200.0/22',
    // 更多參考: https://www.cloudflare.com/ips/
], true); // 嚴格模式

$clientIp = $resolver->getClientIp();
echo "真實客戶端 IP: $clientIp";

2. Laravel 集成 (TrustProxies 中間件)

// app/Http/Middleware/TrustProxies.php
namespace App\Http\Middleware;

use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;

class TrustProxies extends Middleware {
    protected $proxies = '*'; // 或具體 IP 陣列
    
    protected $headers = Request::HEADER_X_FORWARDED_FOR |
                         Request::HEADER_X_FORWARDED_HOST |
                         Request::HEADER_X_FORWARDED_PORT |
                         Request::HEADER_X_FORWARDED_PROTO |
                         Request::HEADER_X_FORWARDED_AWS_ELB;
}

3. Symfony 集成 (Trusted Proxies)

# config/packages/framework.yaml
framework:
    trusted_proxies: '10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.1/32'
    trusted_headers: ['x-forwarded-for', 'x-forwarded-host', 'x-forwarded-port', 'x-forwarded-proto', 'forwarded']

4. ThinkPHP 6+ 集成

// config/app.php
'request' => [
    // 自動解析代理頭
    'proxy_headers' => ['X-Forwarded-For', 'X-Real-IP', 'Client-IP'],
    // 受信任代理 (支持 CIDR)
    'trusted_proxies' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'],
],

代理類型與 IP 表現對照

代理類型REMOTE_ADDRX-Forwarded-ForX-Real-IPForwarded識別難度
無代理客戶端 IP無/空無/空無/空簡單
透明代理代理 IP客戶端 IP, 代理1, 代理2...客戶端 IPfor=客戶端 IP中等
匿名代理代理 IP代理 IP, 代理1...代理 IPfor=代理 IP困難
欺騙代理代理 IP偽造 IP, 代理1...偽造 IPfor=偽造 IP極難
高匿代理代理 IP無/空無/空無/空不可識別
CDN (Cloudflare等)CDN 邊緣 IP客戶端 IP, CDN IP...客戶端 IPfor=客戶端 IP簡單 (需信任 CDN IP)
K8s Service MeshSidecar IP客戶端 IP, Sidecar...客戶端 IPfor=客戶端 IP中等 (需信任 Pod CIDR)

安全防偽最佳實踐

  1. 必須配置受信任代理 CIDR:未配置嚴格模式下任何頭部均可偽造
  2. 優先使用 RFC 7239 Forwarded 頭部:標準化、防範注入、支持 IPv6
  3. 配置 Web 服務器清理不可信頭部:
    # 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; # 遞歸查找真實客戶端 IP
  4. 應用層二次驗證:關鍵操作 (登錄、支付) 記錄 IP 與設備指紋綁定
  5. 日誌記錄完整鏈路:存儲 REMOTE_ADDR、所有代理頭部、解析結果,便於審計

IPv6 特殊處理

// IPv6 地址標準化存儲
function normalizeIpv6(string $ip): string {
    if (strpos($ip, ':') === false) return $ip; // IPv4
    if (!function_exists('inet_pton')) return $ip;
    $bin = inet_pton($ip);
    return $bin ? inet_ntop($bin) : $ip; // 展開為完整格式
}

// 數據庫存儲建議
// VARCHAR(45) 可存 IPv4 (15) + IPv6 (39) + 輸留
// 或兩欄: ipv4 VARBINARY(4), ipv6 VARBINARY(16)
function ipToBinary(string $ip) {
    return inet_pton($ip); // 返回二進制字符串
}

function binaryToIp(string $bin) {
    return inet_ntop($bin);
}
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved