1. 程式人生 > >nginx 如何配置來獲取使用者真實IP

nginx 如何配置來獲取使用者真實IP

##1.背景知識

1.1. 前提知識點:

還有nginx中的幾個變數:

  • remote_addr

    代表客戶端的IP,但它的值不是由客戶端提供的,而是服務端根據客戶端的ip指定的,當你的瀏覽器訪問某個網站時,假設中間沒有任何代理,那麼網站的web伺服器(Nginx,Apache等)就會把remote_addr設為你的機器IP,如果你用了某個代理,那麼你的瀏覽器會先訪問這個代理,然後再由這個代理轉發到網站,這樣web伺服器就會把remote_addr設為這臺代理機器的IP,除非代理將你的IP附在請求header中一起轉交給web伺服器。

  • X-Forwarded-For(簡稱XFF)

    X-Forwarded-For 是一個 HTTP 擴充套件頭部。HTTP/1.1(RFC 2616)協議並沒有對它的定義,它最開始是由 Squid 這個快取代理軟體引入,用來表示 HTTP 請求端真實 IP。如今它已經成為事實上的標準,被各大 HTTP 代理、負載均衡等轉發服務廣泛使用,並被寫入 

    RFC 7239(Forwarded HTTP Extension)標準之中。

    XFF的格式為:

    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。Remote Address 無法偽造,因為建立 TCP 連線需要三次握手,如果偽造了源 IP,無法建立 TCP 連線,更不會有後面的 HTTP 請求。但是在正常情況下,web伺服器獲取Remote Address只會獲取到上一級的IP,本例裡則是proxy3 的 IP3,這裡先埋個伏筆

  • X-Real-IP

    這又是一個自定義頭部欄位,通常被 HTTP 代理用來表示與它產生 TCP 連線的裝置 IP,這個裝置可能是其他代理,也可能是真正的請求端,這個要看經過代理的層級次數或是是否始終將真實IP一路傳下來。(注意:如果未經嚴格處理,可以被偽造

1.2.前提與鐵律

鐵律:當多層代理或使用CDN時,如果代理伺服器不把使用者的真實IP傳遞下去,那麼業務伺服器將永遠不可能獲取到使用者的真實IP。

1.3.使用者真實IP的來源和現實情況

首先說使用者真實的IP也會存在很多人共用一個IP的情況。使用者的請求到達業務伺服器會經過以下幾種情形:

####1.3.1.寬頻供應商提供獨立IP

比如家裡電信寬頻上網,電信給分配了公網ip,那麼一個請求經過的ip路徑如下:

192.168.0.101(使用者電腦ip)–>192.168.0.1/116.1.2.3(路由器的區域網ip及路由器得到的電信公網ip)–>119.147.19.234(業務的前端負載均衡伺服器)–>192.168.126.127(業務處理伺服器)。

這種情況下,119.147.19.234會把得到的116.1.2.3附加到頭資訊中傳給192.168.126.127,因此這種情況下,我們取得的使用者ip則為:116.1.2.3。如果119.147.19.234沒有把116.1.2.3附加到頭資訊中傳給業務伺服器,業務伺服器就只能取上上一級的110.147.19.234.

1.3.2.寬頻供應商不能提供獨立IP

寬頻提供商沒有足夠的公網ip,分配的是個內網ip,比如長寬等小的isp。請求路徑則可能為:

192.168.0.123(使用者電腦ip)–>192.168.0.1/10.0.1.2(路由器的區域網ip及路由器得到的運營商內網ip)–>211.162.78.1(網路運營商長城寬頻的公網ip)–>119.147.19.234(業務的前端負載均衡伺服器)–>192.168.126.127(業務處理伺服器)。
這種情況下得到的使用者ip,就是211.162.78.1。 這種情況下,就可能出現一個ip對應有數十上百個使用者的情況了(受運營商提供的代理規模決定,比如可能同時有幾千或上萬的寬頻使用者都是從211.162.78.1這個ip對外請求)。

####1.3.3.手機2g上網
網路提供商沒法直接提供ip給單個使用者終端,以中國移動cmwap上網為例,因此請求路徑可能為:

手機(手機上沒法檢視到ip)–> 10.0.0.172(cmwap代理伺服器ip)–>10.0.1.2(移動運營商內網ip)–>202.96.75.1(移動運營商的公網ip)–>119.147.19.234(業務的前端負載均衡伺服器)–>192.168.126.127(業務處理伺服器)。
這種情況下得到的使用者ip,就是202.96.75.1。2008年的時候整個廣東聯通就三個手機上網的公網ip,因此這種情況下,同一ip出現數十萬使用者也是正常的。

####1.3.4.大廠,有幾萬或數十萬員工,但是出口上網ip就一個
這種也會出現來自同一ip的超多使用者,比如騰訊、百度等某一個辦公區,可能達到幾萬人,但出口IP可能就那麼幾個。

2.如何獲取使用者真實IP

2.1. 當業務伺服器直接暴露在公網上,並且未使用CDN和反向代理伺服器時:

可以直接使用remote_addr。如 PHP 可以直接使用

$_SERVER['REMOTE_ADDR']

這時候,HTTP_X_FORWARDED_FOR 和 HTTP_X_REAL_IP 都是可以被偽造的,但REMOTE_ADDR是客戶端和伺服器的握手IP,即client的出口IP,偽造不了。
比如用下面這條命令來請求一個php檔案,並且輸出$_SERVER資訊

curl http://10.200.21.32/test.php -H 'X-Forwarded-For: unkonw, <alert>aa,11.22.33.44,11</alert>" 1.1.1.1' -H 'X-Real-IP: 2.2.2.2, <a>'

結果是(只取部分資訊,10.100.11.25是我電腦的IP,伺服器是內網伺服器,所以不會有公網IP)

[HTTP_X_FORWARDED_FOR] => unkonw, <alert>aa,11.22.33.44,11</alert>" 1.1.1.1
[REMOTE_ADDR] => 10.100.11.25
[HTTP_X_REAL_IP] => 2.2.2.2, <a>

可以看到,HTTP_X_FORWARDED_FOR 和 HTTP_X_REAL_IP 是萬萬不可直接拿來用的。使用$remote_addr是明智的選擇。

比如我們偽造一下來源IP發給著名的 ip138.com

curl http://1212.ip138.com/ic.asp -H 'X-Forwarded-For: unkonw, <alert>aa,11.22.33.44,11</alert>" 1.1.1.1'

它原樣輸出了我們偽造的XFF。

2.2.在代理伺服器或CDN之後的業務伺服器

前提:上面的每一層代理或CDN,都將原始請求的 remote_addr 一路傳遞下去。我們先來看其中一種方案。

如果web伺服器上層也是使用nginx做代理或負載均衡,則需要在代理層的nginx配置中明確XFF引數,累加傳遞上一個請求方的IP到header請求中。以下是代理層的nginx配置引數。

   proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;

如果web伺服器前面使用了HAProxy,則需要增加以下配置來將使用者的真實IP轉發到web伺服器。

    option forwardfor

如果想在業務伺服器獲取完整的鏈路資訊,還是通過XFF獲取,需要在nginx的配置中加一條配置,加上此配置可以讓我們獲取整個鏈路資訊:

fastcgi_param  HTTP_X_FORWARDED_FOR $http_x_forwarded_for;

實測此引數最好加在被 include 的fastcgi.conf中,就是有一堆fastcgi_param配置的那個檔案否則就寫入location段。這個配置可能會影響你的nginx日誌,這個後續會詳細說明。如果不配置此項,則我們在WEB SERVER 上直接獲取到的XFF資訊則是上一個代理層的IP。當然,也不影響獲取使用者真實IP。不過如果你是在除錯配置的情況下,就不方便檢視整個鏈路了。

2.2.1 在只有一層代理的情況下

我們按上面的配置發起一個偽造請求, 10.100.11.25 是我電腦的IP,鏈路為:

10.100.11.25(client)->10.200.21.33(Proxy)->10.200.21.32(Web Server)

curl 請求:

curl http://10.200.21.33:88/test.php -H 'X-Forwarded-For: unkonw, <8.8.8.8> 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'

結果如下:

[HTTP_X_FORWARDED_FOR] => unkonw, <8.8.8.8> 1.1.1.1, 10.100.11.25
[REMOTE_ADDR] => 10.200.21.33
[HTTP_X_REAL_IP] => 10.100.11.25

我們可以看到,XFF被附加上了我的IP,但前面的一系列偽造內容,可以輕易騙過很多規則,而HTTP_X_REAL_IP 則傳遞了我電腦的IP。因為在上面的配置中,X-Real-IP 已經被設定為握手 IP。 但多層代理之後,以上面的規則,顯然 HTTP_X_REAL_IP 也不會是真實的使用者IP了。而 HTTP_X_FORWARDED_FOR 則在原有資訊(我們偽造的資訊)之後附上了握手 IP 一起傳遞過來了。

2.2.2 在兩層或更多代理的情況下

我們這裡只測試兩層,實際鏈路為:

10.100.11.25(client)->10.200.21.34(Proxy)->10.200.21.33(Proxy)->10.200.21.32(Web Server)

Curl 命令:

curl http://10.200.21.34:88/test.php -H 'X-Forwarded-For: unkonw, <8.8.8.8> 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'

兩層代理的情況下結果為:

[HTTP_X_FORWARDED_FOR] => unkonw, <8.8.8.8> 1.1.1.1, 10.100.11.25, 10.200.21.34
[REMOTE_ADDR] => 10.200.21.33
[HTTP_X_REAL_IP] => 10.200.21.34

根據上面的情況,怎麼挑出真正的使用者IP呢?設想三種方案:

1.   第一層代理將使用者的真實 IP 放在 X-Real-IP 中傳遞下去,後面的每一層都使用 X-Real-IP 繼續往下傳遞。配置為:

  proxy_set_header X-Real-IP $remote_addr;     # 針對首層代理,拿到真實IP
  proxy_set_header X-Real-IP $http_x_real_ip;  # 針對非首層代理,一直傳下去
2. 從首層開始,將使用者的真實IP 放在 X-Forwarded-For 中後面的每一層都使用 X-Forwarded-For繼續往下傳遞。配置為:
    從首層開始,將使用者的真實IP 放在 X-Forwarded-For 中,而不是累加各層伺服器的 IP,但這樣也不夠合理,因為丟掉了整個鏈路資訊。配置為:
   proxy_set_header X-Forwarded-For $remote_addr; # 針對首層代理
  針對非首層代理,則可以用逐步累加的方法。配置為:
proxy_set_header X-Forwarded-For $http_x_forwarded_for; # 針對非首層代理
     從 X-Forwarded-For 中獲取的使用者真實IP,排除掉所有代理IP,取最後一個符合IP規則的,注意不是第一個,因為第一個可能是被偽造的(除非首層代理使用了握手會話 IP 做為值向下傳遞)。

一般CDN都會將使用者的真實 IP 在XFF中傳遞下去。我們可以做幾個簡單的測試就能知道我們該怎麼做。

注意:nginx配置的這兩個變數:
$proxy_add_x_forwarded_for 會累加代理層的IP向後傳遞
$http_x_forwarded_for 僅僅是上層傳過來的值

3.配合nginx realip模組獲取使用者真實IP

我們應該秉承一個原則:

能通過配置讓事情變的更簡單和通用的事兒,就不要用程式去解決。即環境對程式透明。這當然少不了系統運維人員的辛苦。

如果能在配置中理清,就不必用複雜的程式去解決,因為Server上可能有各種應用都要來獲取使用者IP,如果規則不統一,結果會不一致。
程式不知道鏈路到底經過了幾層才轉到web server上,所以讓程式去做相容並不是個好主意。索性就讓程式把所有的代理都當成透明的好了。

終於說到重點了。上面介紹的三種方法中,如果不能保證前面的代理層使用我們指定的規則,這時候怎麼辦呢?

只能使用第三種方法( 即:配合 nginx realip 模組獲取使用者真實IP)。

我們將各層代理的IP排除在外,就取到了真實的使用者IP。這個可以使用nginx的一個模組  realip_module 來實現。

原理是從XFF中拋棄指定的代理層 IP,那麼最後一個符合規則的就是使用者 IP。也可以配合第一起方法一起使用。

但無論如何,首層代理的規則最重要,直接影響後面的代理層和web service的接收結果。

nginx realip_module 模組需要在編譯nginx的時候加上引數--with-http_realip_module

然後在nginx配置中增加以下配置(可以在http,server或location段中增加)

    # set user real ip to remote addr
    set_real_ip_from   10.200.21.0/24;
    set_real_ip_from   10.100.23.0/24;
    real_ip_header     X-Forwarded-For;
    real_ip_recursive on;

set_real_ip_from 後面是可信 IP 規則,可以有多條。如果啟用CDN,知道CDN的溯源IP,也要加進來,除排掉可信的,就是使用者的真實IP,會寫入 remote addr這個變數中。

比如在PHP中可以使用$_SERVER['REMOTE_ADDR'] 來獲取。而WEB SERVER 不使用任何反向代理時,也是取這個值,這就達到了我們之前所說的原則。

real_ip_recursive 是遞迴的去除所配置中的可信IP。如果只有一層代理,也可以不寫這個引數。

然後我在外網請求一下,結果是這樣的

[HTTP_X_FORWARDED_FOR] => unkonw, <8.8.8.8> 1.1.1.1, 112.193.23.51, 10.200.21.50
[REMOTE_ADDR] => 112.193.23.51

112.193.23.51 是 client 的 IP, 10.200.21.50 是WEB SERVER 前面的負載均衡。 真實IP拿到了。

再說下nginx日誌

如果nginx日誌中記錄了XFF,那麼可能會有一些是我們不想記錄的,比如我們現在使用的預設的nginx日誌格式為:

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

這時候由於XFF裡包含太多資訊,甚至可能是一些偽造的未經過濾的文字,在使用和分析日誌的時候會出現麻煩,所以我們乾脆不記錄它。nginx 的日誌格式log_format還有一個預設值“combined”. 預設格式為:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

我們使用這個格式就好了。

總結

我們建議使用以下規則:
首層代理將握手 IP 附在 X-Forwarded-For 上一直向後傳遞(或者將 X-Forwarded-For 設定為握手 IP 向後傳遞),後面的每一層累加握手 IP 往後傳遞。
首層代理將握手 IP 設定為 HTTP 請求頭的 X-Real-IP 中向後傳遞。後面的每一層原樣傳遞下去(有則原樣傳遞,無則設定為握手 IP )。

握手IP:即請求方的 remote_addr.

運維很重要,首個代理層的處理方式很重要。

在只有運維最清楚網路環境的時候,儘量通過配置對應用透明。減少應用層的複雜判斷。如果環境很複雜,比如使用了CDN,則有可能需要多方協調。

參考資料

           http://tomcat.apache.org/tomcat-7.0-doc/config/valve.html
    • http://nginx.org/en/docs/http/ngx_http_realip_module.html
    • https://www.zhihu.com/question/23810075
    • http://www.cnblogs.com/zhengyun_ustc/archive/2012/09/19/getremoteaddr.html
    • http://zhensheng.im/2013/08/31/1952/MIAO_LE_GE_MI
    • http://stackoverflow.com/questions/25929599
    • http://www.ttlsa.com/nginx/nginx-get-user-real-ip/
    • https://imququ.com/post/x-forwarded-for-header-in-http.html
    • http://freeloda.blog.51cto.com/2033581/1288553
    • http://nginx.org/en/docs/http/ngx_http_log_module.html

相關推薦

nginx 如何配置獲取使用者真實IP

##1.背景知識 1.1. 前提知識點: 還有nginx中的幾個變數: remote_addr 代表客戶端的IP,但它的值不是由客戶端提供的,而是服務端根據客戶端的ip指定的,當你的瀏覽器訪問某個網站時,假設中間沒有任何代理,那麼網站的web伺服器(Nginx,Apa

Nginx反向代理後,tomcat獲取真實IP的方法

Nginx 修改nginx.conf檔案 location / {       proxy_pass http://balance;       proxy_redirect off;  &nbs

用Java獲取訪問者真實IP地址

在JSP裡,獲取客戶端的IP地址的方法是:request.getRemoteAddr(),這種方法在大部分情況下都是有效的。但是在通過了Apache,Squid等反向代理軟體就不能獲取到客戶端的真實IP地址了。如果使用了反向代理軟體,用request.getRemoteAd

使用nginx代理的情況下獲取使用者真實IP

##1.背景知識1.1. 前提知識點:還有nginx中的幾個變數:remote_addr代表客戶端的IP,但它的值不是由客戶端提供的,而是服務端根據客戶端的ip指定的,當你的瀏覽器訪問某個網站時,假設中間沒有任何代理,那麼網站的web伺服器(Nginx,Apache等)就會把

用Java獲取訪問者真實IP地址(來源:收集)

在JSP裡,獲取客戶端的IP地址的方法是:request.getRemoteAddr(),這種方法在大部分情況下都是有效的。但是在通過了Apache,Squid等反向代理軟體就不能獲取到客戶端的真實IP地址了。如果使用了反向代理軟體,用request.getRemoteA

使用nginx反向代理後,應用程式如何獲取使用者真實ip

最近,給部署應用網站的伺服器安裝了nginx,作為客戶端和應用伺服器之間的橋樑。對於Web應用來說,這次HTTP請求的客戶端是Nginx而非真實的客戶端瀏覽器,如果不做特殊處理的話,Web應用會把Nginx當作請求的客戶端,獲取到的客戶端資訊就是Nginx的一些資訊。現需要對

nginx做負載CDN加速獲取真實ip

在不用cdn的情況下,nginx做負載獲取真實ip時,nginx配置如下: proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr

JAVA中經過nginx反向代理獲取客戶端ip獲取相關坐標等信息

IT urn 輸出 代理 nginx 高德 AD color javascrip 關於搜狐新浪ip庫查詢接口的使用 直接輸出訪客ip及所在城市: <script src="http://pv.sohu.com/cityjson?ie=utf-8" >&l

php獲取使用者真實IP和防刷機制

   一. 如何獲取使用者IP地址 public static function getClientIp() { if (getenv('HTTP_CLIENT_IP')) { $ip = getenv('HTT

微信獲取好友真實ip, qq獲取ip,經緯度定位 隱蔽獲取 微信查ip

微信好友ip查詢獲取 QQ好友ip查詢獲取 隱蔽方式 ip獲取不需要好友任何操作 不需要點選任何東西 即可獲取真實的ip地址 經緯度跳轉定位 可利用XML欺騙對方點選 header("Location: url:xxxxxx.png "); 以下是成品 只要你會搭建

Linux環境下伺服器利用組播獲取客戶端IP

單播是兩個主機之間端對端通訊(比如TCP、UDP通訊),而廣播用於一個主機對整個區域網中所有主機的通訊。單播和廣播是兩個極端,要麼對一個主機通訊,要麼對區域網內所有主機通訊。然而在實際情況下,比如要獲取區域網內獲取執行特殊任務主機IP,單播顯然不適用,因為單播必須首先要知道通

代理、轉發等多種場景下,如何獲取使用者真實IP

1 概述 工作中會經常碰到需要進行轉發之類的需求,比如LVS轉發、NAT轉發,或者在BGP網路架設一個埠轉發來提高小運營商網路玩家的網路體驗等。 BGP進行遊戲埠轉發之前提到過,架構也比較簡單清晰明瞭: 這種簡單的轉發架構可以在很多地方應用。不過這裡有個源IP識別的問題,A使用者通過B機器的優質網

javaweb獲取使用者真實ip

獲取使用者真實IP地址,不使用request.getRemoteAddr();的原因是有可能使用者使用了代理軟體方式避免真實IP地址,  可是,如果通過了多級反向代理的話,X-Forwarded-For的值並不止一個,而是一串IP值,究竟哪個才是真正的使用者端的真實IP呢?

根據HttpServletRequest獲取使用者真實IP地址

原因:   當我們通過request獲取客戶端IP時,自身伺服器通常會為了保護資訊或者負載均衡的目的,對自身伺服器做反向代理。此時如果我們通過request.getRemoteAddr();可能獲取到的是自身代理伺服器的IP,而無法達到獲取使用者請求ip的目的。  

獲取使用者真實ip

ip /** * 獲取真實ip * @author chenp * @param request * @return */ public String getReallyIp(HttpServletRequest request) { S

nginx代理後,獲取request的ip

應用程式部署上線,一般都會用nginx之類的來進行反向代理,而不是直接訪問tomcat之類的容器. 這時候如果用平時的獲取ip的程式碼,就只會獲取到nginx所在伺服器的ip, 就失去了本身的意義. 今天就來配置下 nginx+tomcat 後,  程式獲取ip和 tomcat的訪問日誌loca

android 根據網路獲取外網ip地址及國家,地區的介面

新浪的IP地址查詢介面:http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js 新浪多地域測試方法:http://int.dpool.sina.com.cn/iplookup/iplookup.php?form

如何通過Request獲取使用者真實IP

在Servlet裡,獲取客戶端的IP地址的方法是:request.getRemoteAddr(),這種方法在大部分情況下都是有效的。但是在通過了Apache,Squid,Nginx等反向代理軟體就不能獲取到客戶端的真實IP地址了。 如果使用了反向代理軟體,例如將http:

java獲取本地真實ip

一、問題在獲取本地ip這個問題上遇見了問題。1、InetAddress.getLocalHost()得到的是VMnet8的ip地址,而並非本地真實ip。2、InetAddress.getLocalHost().getHostAddress()獲取到的是127.0.0.1二、解

PHP獲取使用者真實 IP , 淘寶IP介面獲得ip地理位置

自己不需ip庫,免更新。  淘寶IP庫: http://ip.taobao.com  多謝5樓提醒 ​ /**  * 獲取使用者真實 IP  */ function getIP() {     static $realip;     if (isset($_SERVER