Nginx realip模塊之 $remote_addr 和 X-Forwarded-For

一個請求肯定是可以分爲請求頭和請求體的,而我們客戶端的IP地址信息一般都是存儲在請求頭裏的。如果你的服務器有用Nginx做負載均衡的話,你需要在你的location裏面配置X-Real-IPX-Forwarded-For請求頭:

    location ^~ /your-service/ {
            proxy_set_header        X-Real-IP       $remote_addr;
            proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://localhost:60000/your-service/;
    }

關於X-Real-IP

經過反向代理後,由於在客戶端和web服務器之間增加了中間層,因此web服務器無法直接拿到客戶端的ip,通過$remote_addr變量拿到的將是反向代理服務器的ip地址。

——《實戰nginx》

這句話的意思是說,當你使用了nginx反向服務器後,在web端使用request.getRemoteAddr()(本質上就是獲取$remote_addr),取得的是nginx的地址,即$remote_addr變量中封裝的是nginx的地址,當然是沒法獲得用戶的真實ip的。但是,nginx是可以獲得用戶的真實ip的,也就是說nginx使用$remote_addr變量時獲得的是用戶的真實ip,如果我們想要在web端獲得用戶的真實ip,就必須在nginx裏作一個賦值操作,即我在上面的配置:

proxy_set_header X-Real-IP $remote_addr;

$remote_addr 只能獲取到與服務器本身直連的上層請求ip,所以設置$remote_addr一般都是設置第一個代理上面;但是問題是,有時候是通過cdn訪問過來的,那麼後面web服務器獲取到的,永遠都是cdn 的ip 而非真是用戶ip,那麼這個時候就要用到X-Forwarded-For 了,這個變量的意思,其實就像是鏈路反追蹤,從客戶的真實ip爲起點,穿過多層級的proxy ,最終到達web 服務器,都會記錄下來,所以在獲取用戶真實ip的時候,一般就可以設置成,proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 這樣就能獲取所有的代理ip 客戶ip。

關於X-Forwarded-For

X-Forwarded-For變量,這是一個squid開發的,用於識別通過HTTP代理或負載平衡器原始IP一個連接到Web服務器的客戶機地址的非rfc標準,如果有做X-Forwarded-For設置的話,每次經過proxy轉發都會有記錄,格式就是client1,proxy1,proxy2以逗號隔開各個地址,由於它是非rfc標準,所以默認是沒有的,需要強制添加。在默認情況下經過proxy轉發的請求,在後端看來遠程地址都是proxy端的ip 。也就是說在默認情況下我們使用request.getAttribute("X-Forwarded-For")獲取不到用戶的ip,如果我們想要通過這個變量獲得用戶的ip,我們需要自己在nginx添加配置:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

意思是增加一個$proxy_add_x_forwarded_forX-Forwarded-For裏去,注意是增加,而不是覆蓋,當然由於默認的X-Forwarded-For值是空的,所以我們總感覺X-Forwarded-For的值就等於$proxy_add_x_forwarded_for的值,實際上當你搭建兩臺nginx在不同的ip上,並且都使用了這段配置,那你會發現在web服務器端通過request.getAttribute("X-Forwarded-For")獲得的將會是客戶端ip和第一臺nginx的ip。

那麼$proxy_add_x_forwarded_for又是什麼?

$proxy_add_x_forwarded_for變量包含客戶端請求頭中的X-Forwarded-For$remote_addr兩部分,他們之間用逗號分開。

舉個例子,有一個web應用,在它之前通過了兩個nginx轉發,www.linuxidc.com即用戶訪問該web通過兩臺nginx。

在第一臺nginx中,使用:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

現在的$proxy_add_x_forwarded_for變量的X-Forwarded-For部分是空的,所以只有$remote_addr,而$remote_addr的值是用戶的ip,於是賦值以後,X-Forwarded-For變量的值就是用戶的真實的ip地址了。

到了第二臺nginx,使用:

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

現在的$proxy_add_x_forwarded_for變量,X-Forwarded-For部分包含的是用戶的真實ip,$remote_addr部分的值是上一臺nginx的ip地址,於是通過這個賦值以後現在的X-Forwarded-For的值就變成了“用戶的真實ip,第一臺nginx的ip”,這樣就清楚了吧。

realip模塊

realip模塊的作用是:當本機的nginx處於一個反向代理的後端時獲取到真實的用戶IP。

如果沒有realip模塊,nginx的access_log裏記錄的IP會是反向代理服務器的IP,PHP中$_SERVER[‘REMOTE_ADDR’]的值也是反向代理的IP。

而安裝了realip模塊,並且配置正確,就可以讓nginx日誌和php的REMOTE_ADDR都變成真實的用戶IP。

舉一個最簡單的例子:

如圖使用slb做反向代理服務器對外提供服務,後端使用nginx提供web服務:

  • 如果不使用realip模塊,nginx中拿到的客戶端ip是slb的服務器ip地址100.122.59.106,即 $remote_addr 是 100.122.59.106。

  • 如果使用realip模塊,配置內容如下,此時nginx中拿到的客戶端ip是用戶真實ip地址124.240.3.71,即$remote_addr 是 124.240.3.71。

    set_real_ip_from 100.122.0.0/10; 
    real_ip_header X-Forwarded-For;
    

set_real_ip_from 指令的作用是告訴nginx,100.122.0.0/10 這個ip段是我們的反代服務器(信任服務器),不是真實的用戶IP,real_ip_heaer 則是告訴nginx真正的用戶IP是存在X-Forwarded-For 請求頭中。

使用realip模塊並重新加載nginx配置之後,就可以看到nginx日誌裏記錄的$remote_addr就是124.240.3.71 了,php裏的REMOTE_ADDR也是124.240.3.71。

瞭解:

realip模塊還提供了另外一個指令real_ip_recursive,可以用來處理更加複雜的情況。

首先要明確一點,realip模塊生效的前提是:直接連接nginx的ip是在set_real_ip_from中指定的。

當real_ip_recursive爲off時,nginx會把real_ip_header指定的HTTP頭中的最後一個IP當成真實IP。

當real_ip_recursive爲on時,nginx會把real_ip_header指定的HTTP頭中的最後一個不是信任服務器的IP當成真實IP。

$remote_addr 和 X-Forwarded-For應用場景

某服務採用的是LNMP架構,如下圖:

現在需求如下:

  1. 部分內部接口禁止外部直接調用,且內部調用時無需接口校驗,安全起見,需要設置訪問白名單;
  2. 獲取用戶地理位置。

實現方法:

  • 不使用realip模塊,此時PHP中使用 $_SERVER['REMOTE_ADDR'] 拿到的 $remote_addr 是SLB服務器的ip,直接把 $remote_addr 作爲ip白名單;
  • 定位功能需要獲取用戶真實ip,因爲沒使用realip模塊,此時無法通過 $remote_addr 獲取用戶真實ip,那麼怎麼辦?答案是:在PHP中通過$_SERVER['HTTP_X_FORWARDED_FOR']來獲取realip。
  • 如此一來互不衝突。

至於nginx日誌中記錄哪些信息,爲了定位問題方便,日誌格式爲:

log_format main '$remote_addr - [$time_local] $scheme "$request" $status $upstream_status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" ["$request_time", "$upstream_response_time"] $http_x_forwarded_for "$http_cookie"';
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章