從Header中X-Forwarded-For獲取的IP一定是真實IP嗎?

前言

在實際項目中,用戶ip的獲取很重要。通過報障用戶的ip來快速定位用戶的請求日誌,還可以通過ip訪問頻率來進行防盜鏈處理。在有些項目中,比如之前我們說過的升級,通過用戶ip尾號進行一部分用戶的灰度升級,還比如通過ip來區分用戶的地域,進行個性化的推薦等。一般獲取ip的方式。都是通過Header中的X-Forward-For、X-Real-IP或Remote addr等屬性獲取,但是如果確保獲取到的ip是真實的用戶ip呢?本篇繼續解密!

 

概念

  • Remote Address
    是nginx與客戶端進行TCP連接過程中,獲得的客戶端真實地址。Remote Address 無法僞造,因爲建立 TCP 連接需要三次握手,如果僞造了源 IP,無法建立 TCP 連接,更不會有後面的 HTTP 請求。

  • X-Real-IP
    是一個自定義頭。X-Real-Ip 通常被 HTTP 代理用來表示與它產生 TCP 連接的設備 IP,這個設備可能是其他代理,也可能是真正的請求端。需要注意的是,X-Real-Ip 目前並不屬於任何標準,代理和 Web 應用之間可以約定用任何自定義頭來傳遞這個信息。

  • X-Forwarded-For
    X-Forwarded-For 是一個擴展頭。HTTP/1.1(RFC 2616)協議並沒有對它的定義,它最開始是由 Squid 這個緩存代理軟件引入,用來表示 HTTP 請求端真實 IP,現在已經成爲事實上的標準,被各大 HTTP 代理、負載均衡等轉發服務廣泛使用,並被寫入 RFC 7239(Forwarded HTTP Extension)標準之中。

X-Forwarded-For請求頭格式非常簡單,就這樣:

 X-Forwarded-For:client, proxy1, proxy2

可以看到,XFF 的內容由「英文逗號 + 空格」隔開的多個部分組成,最開始的是離服務端最遠的設備 IP,然後是每一級代理設備的 IP。

如果一個 HTTP 請求到達服務器之前,經過了三個代理 Proxy1、Proxy2、Proxy3,IP 分別爲 IP1、IP2、IP3,用戶真實 IP 爲 IP0,那麼按照 XFF 標準,服務端最終會收到以下信息:

X-Forwarded-For: IP0, IP1, IP2

Proxy3 直連服務器,它會給 XFF 追加 IP2,表示它是在幫 Proxy2 轉發請求。列表中並沒有 IP3,IP3 可以在服務端通過 $remote_address 字段獲得。我們知道 HTTP 連接基於 TCP 連接,HTTP 協議中沒有 IP 的概念,$remote_address 來自 TCP 連接,表示與服務端建立 TCP 連接的設備 IP,在這個例子裏就是 IP3。

詳細分析一下,這樣的結果是經過這樣的流程而形成的:

  • 用戶IP0---> 代理Proxy1(IP1),Proxy1記錄用戶IP0,並將請求轉發個Proxy2時,帶上一個Http Header
    X-Forwarded-For: IP0

  • Proxy2收到請求後讀取到請求有 X-Forwarded-For: IP0,然後proxy2 繼續把鏈接上來的proxy1 ip追加到 X-Forwarded-For 上面,構造出X-Forwarded-For: IP0, IP1,繼續轉發請求給Proxy 3

  • 同理,Proxy3 按照第二部構造出 X-Forwarded-For: IP0, IP1, IP2,轉發給真正的服務器,比如NGINX,nginx收到了http請求,裏面就是 X-Forwarded-For: IP0, IP1, IP2 這樣的結果。所以Proxy 3 的IP3,不會出現在這裏。

  • nginx 獲取proxy3的IP 能通過$remote_address獲取到,因爲這個$remote_address就是真正建立TCP鏈接的IP,這個不能僞造,是直接產生鏈接的IP。

  1.  

很多項目通過獲取 X-Forwarded-For 中首個IP作爲真實IP。但是X-Forwarded-For可以僞造。本文通過以 Nginx 作反向代理時, X-Forwarded-For 及其他獲取真實IP的相關內容。

  •  
請求 -> proxy1 -> proxy2 -> proxy3 -> 後端服務(/hello)

使用$remote_addr

proxy1、2、3在同一臺機器(僅作測試)。使用$remote_addr,以下爲proxy1、proxy2、proxy3日誌格式如下:

log_format proxy1 '"[proxy1]" $remote_addr "$request" $status';log_format proxy2 '"[proxy2]" $remote_addr "$request" $status';log_format proxy3 '"[proxy3]" $remote_addr "$request" $status';

訪問後,日誌如下:

"[proxy1]" 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 "GET /hello HTTP/1.0" 200

結果:proxy1 拿到的是真實IP(36.157.229.110是我的IP),proxy2拿到的是proxy1的IP,proxy3 拿到的是proxy2的IP。

 

使用 X-Forwarded-For

 

在 nginx ngx_http_proxy_module的 proxy_set_header 指令中,可以通過內置變量 KaTeX parse error: Double subscript at position 12: proxy_add_x_̲forwarded_for**…remote_addr 的值追加到 X-Forwarded-For 中。若請求頭中沒有 X-Forwarded-For,那麼 $proxy_add_x_forwarded_for 的值和 $remote_addr 相等。

 

在日誌中打印出 $proxy_add_x_forwarded_for 的值。

log_format proxy1 '"[proxy1]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';log_format proxy2 '"[proxy2]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';log_format proxy3 '"[proxy3]" $remote_addr $proxy_add_x_forwarded_for "$request" $status';

proxy1、proxy2、proxy3 的配置中都加上:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

訪問後,日誌如下(文中有好幾處日誌,看着容易亂,尤其是第二部分$proxy_add_x_forwarded_for的值,需要通過逗號來區分):

"[proxy1]" 36.157.229.110 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 36.157.229.110, 127.0.0.1, 127.0.0.1 "GET /hello HTTP/1.0" 200

結果:

proxy1中,$proxy_add_x_forwarded_for 值與 $remote_addr 相同,都是客戶端的實際IP。

proxy2中,remoteaddr爲proxy1的IP, remote_addr 爲 proxy1的IP,remote addr爲proxy1的IP,proxy_add_x_forwarded_for 中追加了 proxy1的IP,成了36.157.229.110, 127.0.0.1。

proxy3中,$proxy_add_x_forwarded_for 中繼續追加了proxy2的IP,此時,X-Forwarded-For值爲客戶端實際IP, proxy1 IP, proxy2 IP。

因此,此時取 X-Forwarded-For 中第一個IP得到的確實爲客戶端真實IP。

 

僞裝請求鏈路

還是基於上一步的配置,但客戶端請求頭中人爲添加:X-Forwarded-For=192.168.1.1, 192.168.1.2,再看看結果:

"[proxy1]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1, 127.0.0.1 "GET /hello HTTP/1.0" 200

此時,$proxy_add_x_forwarded_for 的值會 基於 X-Forwarded-For 現有值 繼續追加IP。因此,真實IP位於X-Forwarded-For 中哪個位置是不清楚的。

 

如何獲取真實IP?

  • 使用 X-Forwarded-For + Real IP 模塊

可以使用nginx的 ngx_http_realip_module 模塊,從 X-Forwarded-For 或其他屬性中提取真實IP。此處以 X-Forwarded-For 結合該模塊爲例子,需要做兩件事:

一、請求途徑的各代理需要設置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

二、利用 realip 模塊獲取真實IP

這裏proxy3的部分配置(proxy3將請求直接轉發到後端服務),如下:

server {  ...    location / {        set_real_ip_from 127.0.0.1;         real_ip_header    X-Forwarded-For;        real_ip_recursive on;        ...    }}

set_real_ip_from: 表示從何處獲取真實IP(解決安全問題,只認可自己信賴的IP),可以是IP或子網等, 可以設置多個set_real_ip_from。

real_ip_header:表示從哪個header屬性中獲取真實IP。

real_ip_recursive:遞歸檢索真實IP,若從 X-Forwarded-For 中獲取,則需遞歸檢索;若像從X-Real-IP中獲取,則無需遞歸。

基於上一步的測試數據,試驗結果:

"[proxy1]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110 "GET /hello HTTP/1.1" 200"[proxy2]" 127.0.0.1 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1 "GET /hello HTTP/1.0" 200"[proxy3]" 36.157.229.110 192.168.1.1, 192.168.1.2, 36.157.229.110, 127.0.0.1, 36.157.229.110 "GET /hello HTTP/1.0" 200

此時,proxy3 的 $remote_addr 已經拿到了客戶端的真實IP 36.157.229.110,然後 proxy3 將 remote_addr 傳遞到後端服務中去。

 

  • 使用X-Forwarded-For + 安全設置

由於客戶端可以自行傳遞X-Forwarded-For,因此,可以在第一個代理處重置其值,達到忽略客戶端傳遞的X-Forwarded-For的效果。

在 proxy1 中進行如下配置:

proxy_set_header X-Forwarded-For $remote_addr;

使用 X-Real-IP

由於proxy1的 $remote_addr 是客戶端真實IP,因此在 proxy1 中將X-Real-IP的值設置爲 $remote_addr 即可。

proxy_set_header X-Real-IP $remote_addr;

配置下日誌格式(日誌中可以使用 $http_ + 自定義屬性來打印其值):

log_format proxy1 '"[proxy3]" $http_x_real_ip "$request" $status';log_format proxy2 '"[proxy3]" $http_x_real_ip "$request" $status';log_format proxy3 '"[proxy3]" $http_x_real_ip "$request" $status';

結果爲:

"[proxy1]" - "GET /hello HTTP/1.1" 200"[proxy2]" 36.157.229.110 "GET /hello HTTP/1.0" 200"[proxy3]" 36.157.229.110 "GET /hello HTTP/1.0" 200

proxy1 中設置了X-Real-IP的值,proxy2、proxy3日誌中可以看到該值。

 

小結

實際應用中,在代理層處理好客戶端真實IP,開發時直接獲取即可。有些網上的例子,經常先取remoteAddr,然後取X-Real-IP,再取X-Forwarded-For,就屬於代理層不做配置,把細節都丟給了後端服務來處理。

 

關注公衆號:JAVA取經之旅

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章