在現代網路架構中,用戶請求常經過多層代理伺服器(負載平衡器、CDN、反向代理、正向代理等)才抵達應用伺服器。PHP 的 $_SERVER['REMOTE_ADDR'] 僅能取得**直接連線來源的 IP**,若中間有代理,這將是代理伺服器的 IP 而非用戶真實 IP。正確解析真實 IP 需理解各種代理類型與 HTTP 標頭行為差異。
| 代理類型 | REMOTE_ADDR | HTTP_VIA | HTTP_X_FORWARDED_FOR | 匿名程度 |
|---|---|---|---|---|
| 無代理 | 用戶真實 IP | 無/不顯示 | 無/不顯示 | 完全透明 |
| 透明代理 (Transparent Proxies) |
最後一層代理 IP | 代理伺服器 IP | 用戶真實 IP (多層時以逗號分隔) |
低 - 仍傳遞真實 IP |
| 普通匿名代理 (Anonymous Proxies) |
最後一層代理 IP | 代理伺服器 IP | 代理伺服器 IP (隱藏真實 IP,但暴露使用代理) |
中 - 隱藏 IP 但暴露代理行為 |
| 欺騙性代理 (Distorting Proxies) |
代理伺服器 IP | 代理伺服器 IP | 隨機偽造 IP | 中 - 提供假 IP 欺騙目標 |
| 高匿名代理 (High Anonymity / Elite Proxies) |
代理伺服器 IP | 無/不顯示 | 無/不顯示 | 高 - 完全偽裝為代理伺服器直連 |
client, proxy1, proxy2,**最左側通常為最原始客戶端 IP**<?php
function getIp() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
$ip = $_SERVER['REMOTE_ADDR'];
}
return $ip;
}
?>
<?php
/**
* 取得客戶端真實 IP(增強版)
*
* @param array $trustedProxies 受信任的代理 IP/CIDR 陣列(如 ['10.0.0.0/8', '192.168.0.0/16'])
* @return string 客戶端 IP
*/
function getClientIp(array $trustedProxies = []) {
// 1. 如果直接連線非受信任代理,直接回傳 REMOTE_ADDR
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (!empty($trustedProxies) && !isTrustedProxy($remoteAddr, $trustedProxies)) {
return $remoteAddr;
}
// 2. 依優先順序檢查標頭
$headers = [
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
'HTTP_X_FORWARDED',
'HTTP_X_CLUSTER_CLIENT_IP',
'HTTP_FORWARDED_FOR',
'HTTP_FORWARDED',
'REMOTE_ADDR'
];
foreach ($headers as $header) {
if (empty($_SERVER[$header])) continue;
$ips = array_map('trim', explode(',', $_SERVER[$header]));
// 從左至右找第一個非私有/保留 IP
foreach ($ips as $ip) {
if (isValidPublicIp($ip)) {
return $ip;
}
}
}
return $remoteAddr;
}
/** 判斷是否為受信任代理 */
function isTrustedProxy($ip, $trustedProxies) {
foreach ($trustedProxies as $cidr) {
if (ipInCidr($ip, $cidr)) return true;
}
return false;
}
/** CIDR 比對 */
function ipInCidr($ip, $cidr) {
list($subnet, $mask) = explode('/', $cidr);
$subnet = ip2long($subnet);
$ipLong = ip2long($ip);
$mask = -1 << (32 - (int)$mask);
return ($ipLong & $mask) === ($subnet & $mask);
}
/** 驗證是否為合法公開 IP */
function isValidPublicIp($ip) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return false;
}
return true;
}
?>
# nginx.conf
location / {
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;
}
<?php
// 僅信任內部網路代理(例如 10.0.0.0/8、172.16.0.0/12)
$trustedProxies = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
$realIp = getClientIp($trustedProxies);
?>
| CDN 廠商 | 真實 IP 標頭 |
|---|---|
| Cloudflare | CF-Connecting-IP |
| Akamai | True-Client-IP |
| AWS CloudFront | X-Forwarded-For (第一個) |
| Fastly | Fastly-Client-IP |
| CloudFront + ALB | X-Forwarded-For 需解析多層 |
建議在函式中優先檢查廠商專用標頭。
Request::getClientIp()、Laravel 的 $request->ip() 已內建完善處理取得真實 IP 不是單純讀取某個變數,而是**基於網路拓撲的信任鏈驗證**。核心原則:
正確處理 IP 識別,是帳號安全、風控分析、個性化服務的基石。