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

ThinkPHP 獲取客戶端真實 IP 的最佳實踐:框架內置與通用函數雙保險

     編輯:PHP 相關知識

ThinkPHP 獲取客戶端真實 IP 的最佳實踐:框架內置與通用函數雙保險

在代理、負載均衡、CDN 等網絡環境下,$_SERVER['REMOTE_ADDR'] 往往拿到的是代理服務器 IP,而非用戶真實 IP。ThinkPHP 內置 get_client_ip() 並提供通用兜底方案,本文深度解析原理與使用姿勢。

ThinkPHP 內置方法 (推薦優先使用)

// 返回字符串 IP (默認)
$ip = get_client_ip();  // 如 "192.168.1.100"

// 返回整型 IP (適合數據庫存儲/範圍查詢)
$ipLong = get_client_ip(1);  // 如 3232235876
  • 框架版本 ≥ 3.1 內置
  • 自動檢測 HTTP_X_FORWARDED_FOR、HTTP_CLIENT_IP、REMOTE_ADDR 優先級
  • 內置 IP 合法性驗證 (ip2long)

通用獨立函數版 (無框架依賴,可直接複製到項目工具類)

/**
 * 獲取客戶端真實 IP
 * @param int $type 0=字符串IP(默認) 1=整型IP
 * @return string|int
 */
function get_client_ip($type = 0) {
    $type = $type ? 1 : 0;
    static $ip = null;  // 緩存本次請求結果

    if ($ip !== null) return $ip[$type];

    // 1. 優先級順序:X-Forwarded-For > Client-IP > Remote-Addr
    if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        // 過濾 unknown
        $pos = array_search('unknown', $arr);
        if ($pos !== false) unset($arr[$pos]);
        $ip = trim($arr[0]);  // 取第一個非未知 IP (最原始客戶端)
    } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    } elseif (isset($_SERVER['REMOTE_ADDR'])) {
        $ip = $_SERVER['REMOTE_ADDR'];
    }

    // 2. IP 合法性驗證與轉換
    $long = ip2long($ip);
    $ip = $long ? [$ip, $long] : ['0.0.0.0', 0];  // 無效則歸零

    return $ip[$type];
}

核心邏輯深度解析

HTTP 頭優先級原理

頭部來源可信度說明
X-Forwarded-For 代理/CDN/負載均衡自動添加 高 (但可偽造) 格式:client, proxy1, proxy2,第一個為最原始客戶端
Client-IP 部分代理設置 中 單一 IP
REMOTE_ADDR PHP 直接獲取 TCP 連接對端 最高 (無法偽造) 若無代理即為真實 IP;有代理則為代理 IP

關鍵處理細節

  1. 多級代理取第一個:X-Forwarded-For 可能包含鏈路,首個為用戶真實 IP
  2. 過濾 unknown:部分代理會傳 unknown,需剔除
  3. ip2long 驗證:過濾非法格式 (如 IPv6、異常字符串),無效歸零
  4. 靜態緩存:同一請求多次調用只解析一次
  5. 整型存儲優勢:MySQL INT UNSIGNED 存儲,BETWEEN 範圍查詢極快,節省空間

生產環境進階加固

1. 信任代理白名單 (防偽造)

// 僅信任已知代理 IP 段傳來的 X-Forwarded-For
$trustedProxies = ['10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12'];  // 內網段
$clientIp = $_SERVER['REMOTE_ADDR'];

if (in_trusted_proxy($clientIp, $trustedProxies)) {
    // 來自受信代理,可採信 X-Forwarded-For
    $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    $ip = $xff ? trim(explode(',', $xff)[0]) : $clientIp;
} else {
    // 直連或不可信代理,只信 REMOTE_ADDR
    $ip = $clientIp;
}

function in_trusted_proxy($ip, $cidrs) {
    foreach ($cidrs as $cidr) {
        list($range, $mask) = explode('/', $cidr);
        if ((ip2long($ip) & ~((1 << (32 - $mask)) - 1)) == ip2long($range)) return true;
    }
    return false;
}

2. IPv6 支持

function get_client_ip_v6() {
    $headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
    foreach ($headers as $h) {
        if (!empty($_SERVER[$h])) {
            $ips = array_map('trim', explode(',', $_SERVER[$h]));
            foreach ($ips as $ip) {
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6)) {
                    return $ip;  // 返回首個合法 IP (支持 v4/v6)
                }
            }
        }
    }
    return '0.0.0.0';
}

3. 數據庫存儲建議

-- MySQL 表結構
CREATE TABLE access_log (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    ip_int INT UNSIGNED NOT NULL DEFAULT 0,  -- 存整型
    ip_str VARCHAR(45) NOT NULL DEFAULT '',  -- 存字符串 (兼容 IPv6)
    url VARCHAR(512),
    ua TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_ip (ip_int),
    INDEX idx_time (created_at)
);

-- 插入
INSERT INTO access_log (ip_int, ip_str, url, ua) VALUES (?, ?, ?, ?);
// PHP: [$ipLong, $ipStr, $url, $ua]

常見誤區與避坑

誤區真相
直接用 $_SERVER['REMOTE_ADDR']有代理/CDN 時拿到的是代理 IP,非用戶真實 IP
盲信 X-Forwarded-For客戶端可偽造此頭,必須配合受信代理白名單
不驗證 IP 合法性異常字符串導致入庫報錯或查詢異常
存字符串不存整型範圍查詢/索引效率低,IPv4 強烈建議雙存

總結

ThinkPHP 項目優先使用框架內置 get_client_ip(),非框架/需定製化時複製通用函數並加入受信代理白名單驗證。核心原則:優先級排序 + 白名單驗證 + 合法性檢查 + 雙格式存儲。這是獲取真實 IP 的工業級標準姿勢。

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