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

PHP 獲取用戶真實 IP 的 5 種方法:從基礎到高可用方案

     編輯:PHP 相關知識

PHP 獲取用戶真實 IP 的 5 種方法:從基礎到高可用方案

在反向代理、CDN、負載均衡、內網穿透等架構下,$_SERVER['REMOTE_ADDR'] 往往只能拿到代理服務器 IP。本文系統梳理 5 種獲取客戶端真實 IP 的方法,分析適用場景與安全風險,並提供生產環境推薦的封裝函數。

方法對照表

方法核心變量/邏輯適用場景風險
1. 直接讀取$_SERVER['REMOTE_ADDR']無代理、直連 Web 服務器代理環境下獲取到代理 IP
2. HTTP_X_FORWARDED_FOR$_SERVER['HTTP_X_FORWARDED_FOR'] (逗號分隔鏈)標準代理 (Nginx/Apache/CDN) 已配置透傳客戶端可偽造 Header,需信任代理鏈
3. HTTP_CLIENT_IP$_SERVER['HTTP_CLIENT_IP']部分代理/負載均衡設置此 Header非標準,同樣可偽造
4. 遞歸檢測私有 IP解析 XFF 鏈,跳過 10.x/172.16.x/192.168.x 內網段多層代理鏈,需獲取最外層公網 IP邏輯複雜,仍依賴 Header 真實性
5. getenv() 兼容舊版getenv('HTTP_X_FORWARDED_FOR') 等PHP 早期版本/特定 SAPI (CGI/CLI)同方法 2/3,現代 PHP 建議用 $_SERVER

生產環境推薦封裝:信任代理白名單 + 驗證

<?php
/**
 * 獲取客戶端真實 IP(支持 IPv4/IPv6)
 * 
 * @param array $trustedProxies 信任的代理 IP/CIDR 列表,為空則不驗證來源
 * @return string IP 地址或 'unknown'
 */
function getRealIp(array $trustedProxies = []): string
{
    // 1. 直接連接 IP
    $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    
    // 2. 無代理或不可信代理,直接返回
    if (empty($trustedProxies) || !in_array($remoteAddr, $trustedProxies, true)) {
        // 檢查是否為 CIDR 範圍
        foreach ($trustedProxies as $proxy) {
            if (strpos($proxy, '/') !== false && ipInCidr($remoteAddr, $proxy)) {
                break 2; // 跳出雙層循環,繼續解析 Header
            }
        }
        return $remoteAddr;
    }
    
    // 3. 來自信任代理,解析 X-Forwarded-For
    $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    if ($xff) {
        // XFF 格式: "client, proxy1, proxy2" 或單個 IP
        $ips = array_map('trim', explode(',', $xff));
        // 從左向右:第一個非私有/非代理 IP 為真實客戶端
        foreach ($ips as $ip) {
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
        // 全是私有 IP,返回第一個
        return $ips[0] ?? $remoteAddr;
    }
    
    // 4. 兼容 HTTP_CLIENT_IP
    $clientIp = $_SERVER['HTTP_CLIENT_IP'] ?? '';
    if ($clientIp && filter_var($clientIp, FILTER_VALIDATE_IP)) {
        return $clientIp;
    }
    
    return $remoteAddr;
}

/**
 * CIDR 範圍匹配 (IPv4/IPv6)
 */
function ipInCidr(string $ip, string $cidr): bool
{
    if (strpos($cidr, '/') === false) {
        return $ip === $cidr;
    }
    [$rangeIp, $bits] = explode('/', $cidr);
    $mask = ~((1 << (128 - $bits)) - 1); // 簡化:僅演示 IPv4 邏輯
    // 完整實現建議使用 Symfony HttpFoundation IpUtils::checkIp()
    return false; // 此處省略完整實現
}

// ========== 使用示例 ==========
// Nginx 反代架構:信任 Nginx 所在服務器 IP
$trusted = ['10.0.0.5', '10.0.0.0/24', '172.16.0.0/16'];
$realIp = getRealIp($trusted);
echo "真實 IP: $realIp";

Nginx/Apache 必備配置(前置條件)

後端 PHP 能拿到真實 IP,前提是前置代理正確設置 Header:

Nginx (nginx.conf / conf.d/)

proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 關鍵:避免客戶端偽造,Nginx 會追加而非覆蓋

Apache (mod_remoteip)

LoadModule remoteip_module modules/mod_remoteip.so
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/24 172.16.0.0/16
# 將 XFF 寫入 REMOTE_ADDR,PHP 直接讀 $_SERVER['REMOTE_ADDR'] 即可

CDN (Cloudflare/Aliyun/Tencent Cloud)

  • Cloudflare: CF-Connecting-IP Header 包含真實 IP,需在 Nginx 中 proxy_set_header X-Real-IP $http_cf_connecting_ip;
  • 阿里云 CDN: Ali-CDN-Real-IP 或標準 XFF

框架內置支持 (開箱即用)

框架方法配置信任代理
Laravel$request->ip()App\Http\Middleware\TrustProxies 設置 $proxies
Symfony$request->getClientIp()framework.trusted_proxies / Request::setTrustedProxies()
Yii2Yii::$app->request->userIPRequest::$trustedHosts (域名) 或覆蓋 getUserIP()
ThinkPHPRequest::instance()->ip()配置 proxy_ip 白名單

安全最佳實踐總結

  1. 必須配置信任代理白名單:只解析來自已知代理 IP 的 XFF/Header,防止客戶端偽造。
  2. 使用框架內置方法:經過安全審計,自動處理 IPv6、CIDR、Header 優先級。
  3. 日誌記錄完整鏈路:記錄 REMOTE_ADDR、X-Forwarded-For、User-Agent,便於事後審計。
  4. 業務敏感操作二次驗證:登錄、支付、改密等關鍵操作結合設備指紋、短信驗證碼,不單依賴 IP。
  5. IPv6 兼容:驗證邏輯、數據庫字段 (VARBINARY(16) 或 VARCHAR(45))、索引均需支持 IPv6。

總結

獲取真實 IP 核心在於信任鏈建立:前置代理正確透傳 Header → 後端驗證來源 IP 在白名單中 → 解析 XFF 取首個公網 IP。直接使用 $_SERVER['HTTP_X_FORWARDED_FOR'] 而不驗證來源是嚴重安全隱患。生產環境請直接使用框架封裝方法並配置 trusted_proxies。

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