在反向代理、CDN、負載平衡器架構下,$_SERVER['REMOTE_ADDR'] 往往是代理伺服器 IP。本文提供完整的真實 IP 取得函數,支援多層代理鏈與 IPv6。
<?php
function getClientIp(): string {
$headers = [
'HTTP_CLIENT_IP', // 標準代理 Header
'HTTP_X_FORWARDED_FOR', // 最常見,可能包含多個 IP(逗號分隔)
'HTTP_X_FORWARDED', // 較少見
'HTTP_X_CLUSTER_CLIENT_IP', // AWS ALB 等
'HTTP_FORWARDED_FOR', // RFC 7239
'HTTP_FORWARDED', // RFC 7239 標準格式
'REMOTE_ADDR', // 最後備援
];
foreach ($headers as $header) {
if (empty($_SERVER[$header])) continue;
// X-Forwarded-For 可能包含多個 IP:client, proxy1, proxy2...
$ips = array_map('trim', explode(',', $_SERVER[$header]));
foreach ($ips as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
// 兜底:回傳 REMOTE_ADDR(可能為私有 IP)
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
?>
<?php
function getClientIpAdvanced(array $trustedProxies = []): string {
// 預設信任私有網段代理
$trustedProxies = array_merge([
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'::1/128',
'fc00::/7',
], $trustedProxies);
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '';
// 如果直接連線非受信代理,直接回傳
if (!ipInRanges($remoteAddr, $trustedProxies)) {
return $remoteAddr;
}
// 從 Header 取得原始 IP
$forwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
$ips = array_map('trim', explode(',', $forwardedFor));
// 從右往左找第一個非信任代理 IP
for ($i = count($ips) - 1; $i >= 0; $i--) {
if (!ipInRanges($ips[$i], $trustedProxies)) {
return $ips[$i];
}
}
return $remoteAddr;
}
function ipInRanges(string $ip, array $cidrs): bool {
foreach ($cidrs as $cidr) {
if (ipInCidr($ip, $cidr)) return true;
}
return false;
}
function ipInCidr(string $ip, string $cidr): bool {
if (strpos($cidr, '/') === false) return $ip === $cidr;
list($rangeIp, $mask) = explode('/', $cidr);
$mask = (int)$mask;
if (strpos($ip, ':') !== false) { // IPv6
$ipBin = inet_pton($ip);
$rangeBin = inet_pton($rangeIp);
for ($i = 0; $i < 16; $i++) {
if ($mask <= 0) break;
$bits = min(8, $mask);
$mask -= $bits;
$byteMask = (0xFF << (8 - $bits)) & 0xFF;
if (($ipBin[$i] & $byteMask) !== ($rangeBin[$i] & $byteMask)) return false;
}
return true;
} else { // IPv4
$ipLong = ip2long($ip);
$rangeLong = ip2long($rangeIp);
$maskLong = -1 << (32 - $mask);
return ($ipLong & $maskLong) === ($rangeLong & $maskLong);
}
}
?>
<?php
// Laravel: 設定 trusted proxies
// App\Http\Middleware\TrustProxies.php
protected $proxies = '*'; // 或具體 IP 陣列
protected $headers = Request::HEADER_X_FORWARDED_ALL;
// Symfony: framework.yaml
framework:
trusted_proxies: '10.0.0.0/8,192.168.0.0/16'
trusted_headers: ['x-forwarded-for', 'x-forwarded-host']
// 原生 PHP 中介層
function applyTrustedProxyMiddleware(array $trustedProxies = []): void {
$_SERVER['CLIENT_IP'] = getClientIpAdvanced($trustedProxies);
}
?>
X-Forwarded-For 可被客戶端偽造。必須確認 REMOTE_ADDR 為受信代理後,才採信 Header 值。
<?php
// 測試 IPv6 解析
$testIps = [
'2001:db8::1',
'::ffff:192.168.1.1', // IPv4-mapped IPv6
'::1',
];
foreach ($testIps as $ip) {
echo "$ip => " . (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? 'Valid' : 'Invalid') . "\n";
}
?>
正確取得客戶端 IP 是訪問控制、風控、日誌審計的基礎,務必根據實際網路拓撲調整信任代理清單。