NGINX雜談——flask_limiter的IP獲取(怎么拿到真實的客戶端IP)


本篇博客將 flask_limiter 作為切入點,來記錄一下自己對 remote_addr 和 proxy_add_x_forwarded_for 兩個變量、X-Real-IP 和 X-Forwarded-For 兩個字段的一些理解。

flask_limiter 的文檔

如果開發過 Flask + NGINX 的項目,又使用了 flask_limiter 做 IP 限制,就有可能會遇上所有用戶共享限制的問題(一個用戶用超次數,另一個用戶也無法使用)。這是因為使用了 flask_limiter 提供的默認 key_func——get_remote_address 經過 NGINX 代理拿不到真實的用戶 IP。接下來,我們通過他的源碼來分析具體的原因,下文可能很長,且沒有直接了當的解決方法,如果只為解決問題,建議搜索其他文章。

flask_limiter 的 IP 獲取

以下是 flask_limiter 獲取 IP 的代碼

def get_remote_address():
    """
    :return: the ip address for the current request (or 127.0.0.1 if none found)
    """
    return request.remote_addr or '127.0.0.1'

可以看到 flask_limiter 是通過 request.remote_addr 獲取的IP。這里的 remote_addr 變量和 NGINX 的 $remote_addr 是一樣的,都是直接從 TCP 連接信息中獲取的,基本上不能被偽造。即使偽造了,TCP 連接都不知道你是誰,無法進行三次握手,那就根本建立不了連接。

所以 remote_addr 可以理解為就是和當前服務正在通信的客戶端的真實 IP 地址。

而如果加入 NGINX 代理,再進行 http 訪問的時候,用戶就不再和 Python 服務直接建立鏈接。正在通信的客戶端就不再是真實的客戶端,而是代理客戶端。

我們通過netstat -n | grep -E '\.2081|\.80'來查看 TCP 連接情況也可以證實這一點。根據輸出可以發現用戶(192.168.17.167)和 NGNIX 服務端(192.168.19.165:80)建立了 TCP 鏈接,NGINX 客戶端(127.0.0.1:64671)和 Python 服務端(127.0.0.1:9001)建立了 TCP 鏈接。所以經過 NGINX 代理,這個時候 Python 服務端拿到的 remote_addr 就是 NGINX 客戶端的 IP。

Proto Recv-Q Send-Q  Local Address          Foreign Address        (state)  
tcp4       0      0  192.168.19.165.80      192.168.17.167.47362   ESTABLISHED
tcp4       0      0  192.168.19.165.80      192.168.17.167.47360   ESTABLISHED
tcp4       0      0  192.168.19.165.80      192.168.17.167.47130   TIME_WAIT  
tcp4       0      0  127.0.0.1.9001         127.0.0.1.64671        TIME_WAIT 

既然我們無法通過 request.remote_addr 來獲取真實的用戶 IP,那我們就只能在 NGINX 代理的時候設置請求頭,然后在 Python 服務端通過請求頭來獲取用戶的真實 IP 了。flask_limiter 的問題就可以通過自定義 key_func 去獲取我們在 NGINX 設置的請求頭來解決。

NGINX 常見的和 IP 有關的設置有兩個:

location /flask/ {
	proxy_pass http://localhost:9001/;
	proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

以上是兩種常見的設置方法,但是我們還是要理清楚他們的原理。這樣我們進行多層 NGINX 代理,或者用別的代理時才能避免錯誤。

X-Forwarded-For

HTTP/1.1(RFC 2616)協議並沒有對 X-Forwarded-For 進行定義,所以 X-Forwarded-For 一直以來都不是標准的HTTP頭信息,IANA 的注冊信息也可以佐證這一點。我看網上其他的一些博客說它后來被寫入 Forwarded HTTP Extension(RFC 7239)標准,甚至連百度百科也說,IETF 在 Forwarded-For HTTP 頭字段標准化草案中正式提出。我印象中 RFC 7239 就是因為 X-Forwarded-For 不標准才提出 Forwarded 這個新的請求頭字段。為此,我去翻看了 RFC 7239 的歷史版本,確認了沒有哪一版 RFC 7239 為 X-Forwarded-For 正名過。如果看過 RFC 6648RFC 7231 的 "8.3.1. Considerations for New Header Fields"小節,就會知道"X-"開頭的頭信息字段其實是不被認可的。

作為一個不標准的請求頭字段,X-Forwarded-For 卻被各大 http 代理、負載均衡等轉發服務追捧,盡管 RFC 7239 提案已經進入 proposed standard 狀態,也無法改變 X-Forwarded-For 的地位。

X-Forwarded-For的工作原理很簡單,只要每一個代理服務都在定義 X-Forwarded-For 時都追加上上一個代理或客戶的 IP(這是真實的,無法被仿造的),就可以記錄下 http 請求鏈中的所有IP地址,以便后續的每個服務訪問。

有一些爬蟲教程會介紹通過修改 X-Forwarded-For 繞過 IP 限制的方法,但這種方法其實是在利用網頁服務開發者的不嚴謹,並不是真的欺騙了網頁服務。正如上文所說,一個合格的代理,是會追加上上一個代理或客戶的IP的。比如 NGINX 的 $proxy_add_x_forwarded_for,就是會包含真實的 IP 的,他的值為客戶端請求頭的 X-Forwarded-For字段的值 + 客戶端的真實 IP。

Eg. 如果客戶端(0.0.0.0)請求頭的 X-Forwarded-For 字段為127.0.0.1,則 proxy_add_x_forwarded_for 為127.0.0.1, 0.0.0.0;如果客戶端(0.0.0.0)請求頭的 X-Forwarded-For字段為127.0.0.1, 196.128.0.1,則 proxy_add_x_forwarded_for 為127.0.0.1, 196.128.0.1, 0.0.0.0。所以設置得當的話,X-Forwarded-For 和 X-Real-IP 都是可以拿到真實的 IP 的。

有一些防偽造 IP 的教程會說要把 X-Forwarded-For 和 X-Real-IP 一樣設置為 $remote_addr,這其實也是不合理的。X-Forwarded-For 和 X-Real-IP 誕生的目的是不一樣的,X-Real-IP 是為了記錄最外一層代理面向的 IP,保障對服務和代理(這里說的代理指的是由網頁服務提供方提供的代理,可以理解為反向代理)這個整體來說,請求來自合法的 IP;X-Forwarded-For 則是為了記錄完整的 IP 鏈,保障對客戶來說,不管他使用什么樣的代理(這里說的代理指的是由網頁服務提供方提供的代理加上用戶自己使用的代理,比如 VPN),只要代理可靠且不以隱匿為目的,網頁服務總能契合他的需求。

Eg. 就像天氣一樣,我們不能因為用戶使用了北京的代理,就給他推北京的天氣,我們要追蹤到最原始的 IP 地址,並且給他推送該IP對應的地區的天氣。

X-Real-IP

如果說 X-Forwarded-For 是一個野孩子,那 X-Real-IP 則更是浮萍漂泊。好歹 NGINX 代理給 X-Forwarded-For 專門設了一個傳遞變量,X-Real-IP 卻只能是 remote_addr 的載體,甚至隨便換個名字也沒有影響,我們只需要起一個 NGINX 支持的,又沒有在標准里的名字就可以,叫阿貓阿狗也行。

location /flask/ {
	proxy_pass http://localhost:9001/;
	proxy_set_header A-MAO-A-GOU $remote_addr;
	proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

這樣設置最后通過 A-MAO-A-GOU 也可以拿到真實的 IP。當然,采取和大家一樣的標准是很重要的。

上文介紹了 X-Forwarded-For 會存整個 IP 鏈,那我們通過 IP 鏈就可以反推會最外層代理(這里說的代理指的是由網頁服務提供方提供的代理,可以理解為反向代理)面對的客戶端了。

比如,X-Forwarded-For 字段的值是0.0.0.0, 1.1.1.1, 2.2.2.2, 3.3.3.3, 4.4.4.4,我們知道3.3.3.3和4.4.4.4是我們的代理服務器,那最外層代理面對的客戶端就是2.2.2.2。

不過一般來講,這樣操作需要開發人員去了解代理的情況,這對開發人員也是一個負擔,如果代理不只是一條鏈路就更麻煩。所以更好、更直接的方法就是商量好一個字段,讓大家都使用它來傳遞信息,比如 X-Real-IP。這樣代理服務通過X-Real-IP給出一個IP,開發人員利用這個IP進行簡單的校驗;而代理服務通過 X-Forwarded-For 給出的IP鏈,開發人員往往取最前的一條,當成客戶的真實IP。

以上就是關於 X-Real-IP 和 X-Forwarded-For 的記錄,希望有生之年可以看到 RFC 7239 這個草案付諸實踐吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM