起因是用 $request->ip() 來獲取 ip 限流,突然遭到大面積誤殺。排查 access.log 日誌,幾乎所有請求的 $remote_addr ,都為某幾個固定 ip。諮詢運維後發現是他悄悄給前端加了 cdn。那為何會產生這種問題呢?
remote_addr 和 http_x_forwarded_for
先來了解一下前置知識。
remote_addr 是真實的與 Web 服務建立連線的 ip。在不經過代理伺服器的時候,能獲取到使用者真實IP,不可偽造。
但多數請求都經過反向代理、CDN加速等服務,再到達 Web 服務。這時候與 Web 服務真實建立連線的就是代理伺服器。相應地 remote_addr 的值也變成了代理伺服器的 ip。那麼經過代理伺服器的服務,如何才能獲取到使用者真實IP呢?
X-Forwarded-For 則應運而生。它是一個 HTTP 擴充套件頭部,用來記錄一個請求途徑伺服器的ip,可以理解為一個 ip 鏈條。每途徑一臺代理伺服器,它會把訪問者的 ip,追加到 X-Forwarded-For 中。
但 X-Forwarded-For 是可以偽造的,客戶端可能從最初發出的請求中 X-Forwarded-For 就攜帶者幾個 ip。
格式如下:
X-Forwarded-For:client_ip, proxy1_ip, proxy2_ip
Laravel 獲取 ip
Symfony 的 Request類中這樣實現 getClientIps()
public function getClientIps()
{
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return [$ip];
}
return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: [$ip];
}
首先,從 REMOTE_ADDR 中獲取了與 Web 服務建立真實連線的客戶端 ip。然後,判斷此 ip 是否是受信任的代理,如果不是則直接返回此 ip。最後,呼叫 getTrustedValues() 來獲取和處理 X-Forwarded-For。
(由於 Laravel 預設是受信任代理是空,所以導致了開篇提到的問題,如何設定信任代理見下文)
getTrustedValues() 主要有兩個引入注目的操作:
如果 X-Forwarded-For 的 ip 鏈條中,也存在受信任的代理 ip,會被過濾。
將過濾後的 ip 鏈條反轉返回;如果過濾後的 ip 鏈條 為空,則返回受信任的代理 ip。
getClientIps() 返回 ip 連結條,與 X-Forwarded-For 順序相反,這時常會讓開發者踩坑。那為什麼會這麼設計呢?
Symfony開發者在註釋中是這麼解釋的:
返回的陣列中,最受信任的 ip 排在第一位,最不受信任的 ip 排在最後一位。“真實”的客戶端 ip 排在最後一位,但它也是最不受信任的。受信任的代理 ip 已被從中移除。
看完你會恍然大悟。
如何食用
假如,一個請求如下:
client->proxy1->proxy2->proxy3->web service
proxy1-3 是我們的代理伺服器
那麼,它的 X-Forwarded-For:client_ip, p1_ip, p2_ip
由於,proxy1-3 是我們的代理伺服器是可信的。
我們將 p1_ip, p2_ip, p3_ip 加入受信任代理。
namespace App\Http\Middleware;
……
class TrustProxies extends Middleware
{
protected $proxies = [
p1_ip, p2_ip, p3_ip
];
……
那麼,getClientIps() 會返回
[
client_ip
]
由於,client_ip 是由 proxy1 代理伺服器追加到 X-Forwarded-For,所以是可信的,可作為使用者的真實IP。
如果,客戶端請求前 X-Forwarded-For 就已有一個 ip(client0_ip)
getClientIps() 會返回
// 信任度從高到底
[
client_ip,
client0_ip
]
client0 有可能是使用者真實的ip,也有可能是自己偽造的 X-Forwarded-For,並不能信任它。
參考文章
www.cnblogs.com/lcawen/articles/92...
部落格:關於 Laravel 使用了 CDN 獲取真實 IP 記錄
PS:寫完後,發現一篇寫的很棒的文章 segmentfault.com/a/119000002208025...
本作品採用《CC 協議》,轉載必須註明作者和本文連結