在 CDN、負載均衡、反向代理、多層網關架構下,$_SERVER['REMOTE_ADDR'] 往往只能拿到代理服務器 IP。本文匯總 6 種獲取真實客戶端 IP 的方法,並提供**生產級封裝函數**,支持信任代理白名單、IPv6、私有 IP 過濾、HTTP 標準頭優先級判斷。
標準代理鏈路會依次添加:
HTTP_CDN_SRC_IP:部分 CDN(如騰訊云、阿里云)自定義頭,最高可信。HTTP_CLIENT_IP:早期代理標準,易偽造。HTTP_X_FORWARDED_FOR (XFF):**標準多層代理頭**,格式 client, proxy1, proxy2,最左側為原始客戶端。REMOTE_ADDR:TCP 連接直接來源(最後一跳代理/客戶端)。<?php
function getRealIp() {
static $realIp = '';
if ($realIp) return $realIp; // 緩存
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
// 1. CDN 專用頭(高可信)
if (!empty($_SERVER['HTTP_CDN_SRC_IP'])) {
$ip = $_SERVER['HTTP_CDN_SRC_IP'];
}
// 2. HTTP_CLIENT_IP
elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}
// 3. X-Forwarded-For:取最左側非私有 IP
elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
foreach ($ips as $xip) {
if (filter_var($xip, FILTER_VALIDATE_IP) && !isPrivateIp($xip)) {
$ip = $xip;
break;
}
}
}
$realIp = $ip;
return $realIp;
}
function isPrivateIp($ip) {
// RFC 1918 私有地址段 + 環回 + 鏈路本地
$ranges = [
'10.0.0.0/8',
'172.16.0.0/12',
'192.168.0.0/16',
'127.0.0.0/8',
'169.254.0.0/16',
'::1/128', // IPv6 環回
'fe80::/10', // IPv6 鏈路本地
'fc00::/7', // IPv6 唯一本地
];
foreach ($ranges as $range) {
if (ipInCidr($ip, $range)) return true;
}
return false;
}
function ipInCidr($ip, $cidr) {
list($rangeIp, $mask) = explode('/', $cidr);
$mask = ~((1 << (32 - $mask)) - 1);
return (ip2long($ip) & $mask) === (ip2long($rangeIp) & $mask);
}
?>
$HTTP_SERVER_VARS(PHP 4.1+)<?php
function getIpLegacy() {
$vars = $GLOBALS['HTTP_SERVER_VARS'] ?? $_SERVER;
$keys = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
foreach ($keys as $key) {
if (!empty($vars[$key])) return $vars[$key];
}
return getenv('HTTP_X_FORWARDED_FOR') ?: getenv('HTTP_CLIENT_IP') ?: getenv('REMOTE_ADDR') ?: 'Unknown';
}
?>
<?php echo $_SERVER['REMOTE_ADDR']; ?>
<?php
$userIp = ($_SERVER['HTTP_VIA'] ?? '')
? ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'])
: $_SERVER['REMOTE_ADDR'];
echo $userIp ?: $_SERVER['REMOTE_ADDR'];
?>
<?php
function getRealIpV5() {
$ip = false;
if (!empty($_SERVER['HTTP_CLIENT_IP']) && filter_var($_SERVER['HTTP_CLIENT_IP'], FILTER_VALIDATE_IP)) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
}
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
if ($ip) array_unshift($ips, $ip);
foreach ($ips as $xip) {
if (filter_var($xip, FILTER_VALIDATE_IP) && !preg_match('#^(10|172\.16|192\.168)\.#', $xip)) {
return $xip;
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
?>
<?php
function getIpEnv() {
$keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($keys as $key) {
$val = getenv($key);
if ($val) return $val;
}
return $GLOBALS['HTTP_SERVER_VARS']['REMOTE_ADDR'] ?? 'Unknown';
}
?>
現代框架(Laravel、Symfony、Slim、Yii)均基于 Symfony\HttpFoundation\Request,內置**受信任代理**機制,徹底解決 IP 偽造問題。
composer require symfony/http-foundation
<?php
use Symfony\Component\HttpFoundation\Request;
Request::setTrustedProxies(
['10.0.0.0/8', '192.168.0.0/16', '172.16.0.0/12'], // 你的代理/CDN IP 段
Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO
);
$request = Request::createFromGlobals();
$realIp = $request->getClientIp(); // 自動應用信任鏈邏輯
echo "真實 IP: $realIp";
?>
X-Forwarded-For 頭。getScheme()、getHost()、getPort(),解決 HTTPS/端口判斷錯誤。<?php
function getClientIpV6() {
$headers = ['HTTP_CDN_SRC_IP', 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'];
foreach ($headers as $h) {
$val = $_SERVER[$h] ?? '';
if ($val) {
$ips = array_map('trim', explode(',', $val));
foreach ($ips as $ip) {
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'];
}
?>
# Nginx set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; real_ip_header X-Forwarded-For; real_ip_recursive on;
X-Forwarded-For、Via、Remote-Addr 入庫,便於事後審計。VARBINARY(16) 或 INET6 存儲,INET_ATON/INET_NTOA 不支持 IPv6。**結論**:新項目**直接接入 Symfony Request**(或框架封裝),配置受信代理白名單,一行代碼解決所有 IP 獲取與偽造問題。老項目引入上述 getRealIp() 函數並部署 Nginx real_ip_module。