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

PHP 取得客戶端真實 IP 與伺服器 IP 完整指南

     編輯:PHP 相關知識

核心變數對照

變數/函數來源可信度適用場景
$_SERVER['REMOTE_ADDR']TCP 連線來源 IP⭐⭐⭐⭐⭐(最可信)直接連線、無 Proxy
$_SERVER['HTTP_X_FORWARDED_FOR']Proxy 標頭(可能多層)⭐⭐(需驗證)Nginx/Apache 反向代理、CDN、負載均衡器後方
$_SERVER['HTTP_CLIENT_IP']部分 Proxy 標頭⭐⭐特定代理環境
$_SERVER['HTTP_X_REAL_IP']Nginx proxy_set_header 設定⭐⭐⭐(若信任 Nginx 設定)Nginx 反代架構
getenv('REMOTE_ADDR')環境變數(CGI/CLI 同步)⭐⭐⭐⭐CLI 腳本、非 Apache 模組模式
gethostbyname()DNS 解析⭐⭐⭐取得伺服器自身對外 IP 或域名解析

最佳實踐:取得客戶端真實 IP 函數

<?php
/**
 * 取得客戶端真實 IP(支援反向代理、CDN)
 *
 * @param array $trustedProxies 信任的代理 IP/CIDR 陣列,預設為空(不信任任何代理標頭)
 * @return string IPv4 或 IPv6 位址
 */
function get_client_ip(array $trustedProxies = []): string {
    // 1. 直接連線或不信任代理:直接回傳 REMOTE_ADDR
    if (empty($trustedProxies)) {
        return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
    }

    // 2. 確認請求來源是否為信任代理
    $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
    $isTrusted = false;
    foreach ($trustedProxies as $proxy) {
        if (filter_var($proxy, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
            // 單一 IP
            if ($remoteAddr === $proxy) { $isTrusted = true; break; }
        } elseif (strpos($proxy, '/') !== false) {
            // CIDR 區段
            if (ip_in_cidr($remoteAddr, $proxy)) { $isTrusted = true; break; }
        }
    }

    if (!$isTrusted) {
        return $remoteAddr; // 非信任代理,忽略 X-Forwarded-For
    }

    // 3. 解析 X-Forwarded-For(格式:client, proxy1, proxy2...)
    $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    if ($xff) {
        $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];
    }

    // 4. 退而求其次:X-Real-IP(Nginx 常用)
    $xri = $_SERVER['HTTP_X_REAL_IP'] ?? '';
    if ($xri && filter_var($xri, FILTER_VALIDATE_IP)) {
        return $xri;
    }

    // 5. 最後退回 REMOTE_ADDR
    return $remoteAddr;
}

/**
 * 判斷 IP 是否在 CIDR 區段內
 */
function ip_in_cidr(string $ip, string $cidr): bool {
    [$range, $netmask] = explode('/', $cidr);
    if (strpos($range, ':') !== false) { // IPv6
        $ipBin = inet_pton($ip);
        $rangeBin = inet_pton($range);
        $mask = ~((1 << (128 - (int)$netmask)) - 1);
        // PHP 整數溢位,改用二進位字串比較
        for ($i = 0; $i < 16; $i++) {
            if (($ipBin[$i] & chr($mask >> (8 * (15 - $i)) & 0xFF)) !== ($rangeBin[$i] & chr($mask >> (8 * (15 - $i)) & 0xFF))) {
                return false;
            }
        }
        return true;
    } else { // IPv4
        $ipLong = ip2long($ip);
        $rangeLong = ip2long($range);
        $mask = -1 << (32 - (int)$netmask);
        return ($ipLong & $mask) === ($rangeLong & $mask);
    }
}

// ─── 使用範例 ───
// Nginx 反代架構,信任 10.0.0.0/8 內部網段
$clientIp = get_client_ip(['10.0.0.0/8', '192.168.0.0/16']);
echo "客戶端真實 IP:$clientIp";

// 取得伺服器對外 IP(需外部服務或解析自身域名)
$serverIp = gethostbyname($_SERVER['SERVER_NAME'] ?? 'localhost');
echo "伺服器 IP:$serverIp";

Nginx/Apache 設定關鍵

Nginx(反代層)

location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_pass https://php_upstream;
}

Apache (mod_remoteip)

RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 10.0.0.0/8 192.168.0.0/16
# 設定後 $_SERVER['REMOTE_ADDR'] 會自動被覆寫為真實客戶端 IP

常見誤區

直接信任 $_SERVER['HTTP_X_FORWARDED_FOR']
攻擊者可偽造標頭繞過 IP 封鎖、竊取帳號。必須驗證請求來源 IP 是否為信任代理。
使用 gethostbyname($_SERVER['SERVER_NAME']) 取得伺服器公網 IP
SERVER_NAME 可能為內網域名或 localhost,解析結果不一定是公網 IP。建議呼叫外部 API(如 https://api.ipify.org)或在部署腳本中寫入環境變數。
IPv6 相容性
所有 IP 處理函數需支援 IPv6(filter_var、inet_pton),資料庫欄位用 VARBINARY(16) 或 INET6 儲存。

總結

  • 單機直連:直接用 $_SERVER['REMOTE_ADDR']。
  • 反代/CDN:在 Web 伺服器層(Nginx/Apache)設定標頭轉發,PHP 端配合 trustedProxies 白名單解析 X-Forwarded-For。
  • 安全第一:永遠驗證代理來源 IP,拒絕未授權的標頭注入。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved