Laravel框架中 getClientIps() 原理和用法

Mr-houzi發表於2020-07-20

起因是用 $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() 主要有兩個引入注目的操作:

  1. 如果 X-Forwarded-For 的 ip 鏈條中,也存在受信任的代理 ip,會被過濾。

  2. 將過濾後的 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 協議》,轉載必須註明作者和本文連結

相關文章