目錄
1 背景
2 常見誤區
3 X-Forwarded-For 解析規則
4 real_ip() 函數 —— 一行代碼落地
5 與框架方法的協同
6 Nginx 端最小配置
7 生產落地 checklist
8 常見 Q&A
9 總結
在反向代理環境下精準獲取客戶端真實 IP 的最佳實踐
— 基于自定義 real_ip()
函數的完整思路與落地方案
1 背景
在生產環境里,Web 服務通常位于負載均衡或 Nginx 反向代理之后。
瀏覽器 → CDN → Nginx → PHP (FPM) 的多層鏈路會導致:
變量 | 實際值 | 問題 |
---|---|---|
$_SERVER['REMOTE_ADDR'] | 127.0.0.1(或 Nginx 節點內網 IP) | 僅表示最后一跳反向代理 |
$request->ip() (ThinkPHP) | 同上 | 未啟用 信任代理 時失真 |
由此帶來日志定位、風控校驗、限流等功能全部“失靈”。
2 常見誤區
-
只改 Nginx
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
頭已經傳了,但 PHP 不解析 → 仍舊拿不到。 -
濫信所有 X-Forwarded-For
該頭可被偽造,直接用來做安全邏輯 → 風險極高。 -
中間件里 late setTrustedProxies()
Request 對象早已初始化,IP 已經確定,再信任也來不及。
3 X-Forwarded-For 解析規則
X-Forwarded-For: <client>, <proxy1>, <proxy2>└──────┘ ← 我們真正想要的
-
最左側 是瀏覽器的公網 IP
-
后續是各級代理依次追加
-
若配合私網過濾,可快速鑒別偽造或內網穿透
4 real_ip() 函數 —— 一行代碼落地
/*** 精準返回客戶端真實 IP** @param \think\Request $request* @return string*/
function real_ip(\think\Request $request): string
{$xff = $request->header('x-forwarded-for');if ($xff) {$ip = trim(explode(',', $xff)[0]); // 取最左側// 過濾私網 & 保留地址,防注入$opts = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;if (filter_var($ip, FILTER_VALIDATE_IP, $opts)) {return $ip;}}// 回落到框架默認(REMOTE_ADDR / 127.0.0.1)return $request->ip();
}
核心思路:
1?? 先嘗試解析X-Forwarded-For
;2?? 利用FILTER_VALIDATE_IP
保證是合法公網 IP;
3?? 任何異常都回退到$request->ip()
,保證業務不崩。
5 與框架方法的協同
場景 | 建議 |
---|---|
風控 / 日志 | 一律用 real_ip() ,保證公網地址唯一可信 |
白名單校驗 | 先 real_ip() ,再比對企業固定 IP 段 |
統計報表 | real_ip() > $request->ip() ,避免把所有人都歸為 127.0.0.1 |
Tip:在 ThinkPHP 里可做 Helper,全局調用:
// app/common.php
function ip(): string
{return real_ip(request());
}
6 Nginx 端最小配置
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-
若有多級反代(CDN → Nginx),應在每一層都 追加 而非覆蓋
X-Forwarded-For
。 -
反代本機 IP 請加入服務端白名單,防止偽造:
$trusted = ['127.0.0.1', '10.0.0.0/8'];
// 如果 REMOTE_ADDR 不在 trusted,拒絕自定義 XFF
7 生產落地 checklist
項 | 是否完成 | 備注 |
---|---|---|
Nginx 傳遞 X-Forwarded-For | ? | $proxy_add_x_forwarded_for |
PHP 層封裝 real_ip() | ? | 私網過濾 + 回退機制 |
安全校驗使用 real_ip() | ? | 登錄限頻、驗證碼、黑名單等 |
日志中記錄 real_ip() | ? | ELK / Loki / Graylog |
8 常見 Q&A
問題 | 解答 |
---|---|
同局域網壓力測試獲取 192.168.*? | 內網環境可取消 FILTER_FLAG_NO_PRIV_RANGE 過濾,或透傳 X-Forwarded-For 私網地址。 |
CDN 已做真實 IP 回注 (True-Client-IP )? | 可把 True-Client-IP 加入 header 檢測鏈,優先級視業務而定。 |
Laravel / Symfony 可以復用嗎? | 同理,只需把 real_ip() 的 $request 類型替換成對應框架的 Request。 |
9 總結
-
Nginx 只負責傳遞頭,PHP 才是最終裁判。
-
自定義
real_ip()
= 可控 + 安全 + 不依賴框架魔術。 -
私網&保留段過濾能有效抵抗偽造,回退邏輯保證全鏈路穩健。
落地后一條日志里看到的將再也不是“127.0.0.1”,而是真正的客戶 IP——為審計、風控、可觀測性打下堅實基礎。