NGINX中的的請求頭x_real_ip和x_forwarded_for


轉自:https://blog.csdn.net/xiaoxiao_yingzi/article/details/92835704

參考:https://www.cnblogs.com/dissipate/p/13336414.html

普及下各個機器的名稱

發送請求方的機器 名稱叫客戶端。
請求轉發和反向代理的機器叫負載均衡或者LB
最終邏輯處理的機器叫WEB機器。【碼農寫的邏輯基本上都在WEB機器上】

先說下我們的測試的機器IP分布。

客戶端IP 100.100.100.1
負載均衡LB 100.100.100.2
web機器 100.100.100.3

 

remote_addr

客戶端的IP,如果有代理的話表示最后一個代理服務器的IP。Nginx變量。這個變量是建立TCP連接的IP變量。remote_addr所表示的IP是不可更改的。試想下,如果這個變量可隨意更改的話,都無法建立正常的TCP連接。
LB上設置

proxy_set_header   REMOTE-ADDR      100.100.100.100;

WEB機器上打印

ngx.log(ngx.ERR,ngx.req.get_headers()['remote_addr'])
ngx.log(ngx.ERR, ngx.var.remote_addr)

[error] 7566#7566: *5914 [lua] test.lua:
proxy_set_header   REMOTE-ADDR      100.100.100.100
[error] 7566#7566: *5914 [lua] test.lua    100.100.100.2

不管怎么設置,最終Nginx變量的值都是建立連接的IP。而且需要注意的是header里面的值和變量里面的值是不相同的

X-Forwarded-For

Nginx變量,如果每個代理服務器都設置了
proxy_set_header X-Forwarded-For則$proxy_add_x_̲forwarded_for是…remote_addr用逗號合起來,如果請求頭中沒有X-Forwarded-For則proxy_add_x_̲forwarded_for為remote。
會記錄請求的路由順序。這個變量只是記錄請求的服務器路由順序。因為這個變量不管在客戶端還是代理服務商都是可以偽造的。

在使用nginx做反向代理時,我們為了記錄整個的代理過程,我們往往會在配置文件中做如下配置:

location / {
       省略...
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://1xx.xxx.xxx.xxx;
    
    }

我們做下測試

ngx.log(ngx.ERR, ngx.var.http_x_forwarded_for)
100.100.100.1 ,100.100.100.2

基本是按照發起方的請求順序記錄的,顯示客戶端IP,然后代理服務器的IP。如果有多層代理的話,就是這樣的

客戶端IP,proxy1,proxy2,proxyN

而且是當前機器追加記錄上一個機器的IP。proxy1追加就客戶端IP,proxy2追加記錄proxy1.
如果請求的時候偽造X-Forwarded-For即加header頭 -H ‘X-Forwarded-For:1.1.1.1,2.2.2.2’。就會是

偽造IP,客戶端IP,proxy1,proxy2,proxyN

所以說取真實IP直接獲取X-Forwarded-For的第一個IP是不合理的。

如果是服務器上,不傳遞X-Forwarded-For,即proxy_set_header X-Forwarded-For 沒有這個。那下一級的X-Forwarded-For這個變量就是空的。所以X-Forwarded-For 這個值主要是proxy_set_header 傳遞。

X-Real-IP

顧名思義真實IP。這個變量主要是用來記錄真實IP。這個值也主要是以來proxy_set_header傳遞。可以先看下使用

100.100.100.2
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

100.100.100.3
ngx.log(ngx.ERR, ngx.var.http_x_forwarded_for)
ngx.log(ngx.ERR, ngx.var.http_x_real_ip)

100.100.100.1,100.100.100.2
100.100.100.1

可以看到,使用得當,x_real_ip是可以很輕松的拿到真實IP。
如果是多級代理的話,一級一級向后傳遞真實IP。

第一級代理寫法
proxy_set_header X-Real-IP $remote_addr;
后面的代理
proxy_set_header X-Real-IP $x_real_ip;

總結

X-Forwarded-For與X-Real-IP 主要依賴proxy_set_header傳遞,所以想傳什么樣的值就傳什么樣的值。Remote-Addr建立連接的IP,有的地方也說是上一跳的IP,這個不依賴header頭傳遞,不可更改。
所以用上面的組合,第一層代理獲取到真實IP,remote_addr。使用x_real_ip層層后傳,使用x_real_ip在WEB機器上獲取到真實IP。
真實IP的獲取順序是 先檢查x_real_ip有無值,有就返回。沒有再檢查Remote-Addr有無值,有就返回。X-Forwarded-For主要記錄請求路由順序,可偽造。

 

下面進入實戰示例!

1.我們測試一下請求經過三層代理的情況,測試設備分配:

  • win10 一台
  • 運行在win10上的虛擬機centos6-0,ip:192.168.247.131,一級代理
  • 運行在win10上的虛擬機centos6-1, ip:192.168.247.132 ,二級代理
  • 運行在win10上的虛擬機centos6-2, ip:192.168.247.133 ,三級代理
  • 雲服務器,應用服務器

2.測試環境配置:

  • win10 在/etc/hosts文件中添加192.168.247.131 http://test.proxy.com
  • centos6-0,ip:192.168.247.131,安裝nginx,把所有請求轉發到192.168.247.132
  • centos6-1, ip:192.168.247.132,安裝nginx,把所有請求轉發到192.168.247.133
  • centos6-2, ip:192.168.247.133,安裝nginx,把所有請求轉發到雲服務器
  • 在雲服務器上的日志中打印http header中的X-Forwarded-For信息
  • 防火牆可以關閉掉,防止win10請求無法進入代理鏈

 

3.nginx配置文件

#centos6-0,ip:192.168.247.131 ,nginx.conflocation

location / {
            root   html;
            index  index.html index.htm index.php;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass http://192.168.247.132;
        }

#centos6-1,ip:192.168.247.132 ,nginx.conf

location / {
      root   html;
      index  index.html index.htm index.php;      #proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_pass http://192.168.247.133;
    } 

#centos6-2,ip:192.168.247.133 ,nginx.conf

location / {
        root   html;
        index  index.html index.htm index.php;  
        #proxy_set_header X-Real-IP $x_real_ip;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://123.206.96.111;
    }

 #雲服務器方便起見在日志中設置打印$http_x_forwarded_for,進行觀察

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

 

4.基於上面的配置在win10瀏覽器輸入:"http://test.proxy.com" 查看雲服務器日志打印結果如下:

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:18:20:27 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"

 192.168.247.1, 192.168.247.131, 192.168.247.132 為$http_x_forwarded_for內容,顯然記錄了代理過程,其中192.168.247.1是客戶端ip

 192.168.247.1 為基於上述設置的真實IP(不一定准確)

 101.254.182.6 公網IP

 

繼續。。。

 我們要仔細測試一下在不同代理服務器設置X-FORWARDED-FOR在應用服務器拿到的$http_x_forwarded_for有何不同

1.只在proxy01設置X-FORWARDED-FOR, 在proxy02,proxy03配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.1|192.168.247.1|101.254.182.6 - - [22/May/2017:18:52:49 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1"

 

2.只在proxy02設置X-FORWARDED-FOR, 在proxy01,proxy03配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.131|192.168.247.1|101.254.182.6 - - [22/May/2017:18:59:59 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.131"

 

 3.只在proxy03設置X-FORWARDED-FOR, 在proxy01,proxy02配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:01:27 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.132"

 

4.只在proxy01,proxy03設置X-FORWARDED-FOR, 在proxy02配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.1, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:05:49 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.132"

 

5.只在proxy02,proxy03設置X-FORWARDED-FOR, 在proxy01配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [22/May/2017:19:08:39 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.131, 192.168.247.132"

 

6.只在proxy01,proxy02設置X-FORWARDED-FOR, 在proxy03配置文件中注釋掉proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

192.168.247.1, 192.168.247.131|192.168.247.1|101.254.182.6 - - [22/May/2017:19:10:40 +0800] "GET /admin/login/?next=%2Fadmin%2F HTTP/1.0" 200 623 "http://test.proxy.com/admin/login/?next=%2Fadmin%2F" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131"

小結:

  1. 通過以上幾種情況我們可以了解到設置X-Forwarded-For是一個可疊加的過程,后面的代理會把前面代理的IP加入X-Forwarded-For,類似於python的列表append的作用.
  2. 我們看到在三層代理情況下無論如何設置,應用服務器不可能從$http_x_forwarded_for拿到與它直連的這台服務器的ip(proxy03 ip),此時我們可以使用$remote_addr(遠程ip,表示直連的那台代理).一句話,當前服務器無法通過$http_x_forwarded_for獲得上級代理或者客戶端的ip,應該使用$remote_addr.
  3. 在代理過程中至少有一個代理設置了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;否則后面代理或者應用服務器無法獲得相關信息.
  4. 注意,應用服務器可以通過$proxy_add_x_forwarded_for客戶端IP(只要至少proxy01代理設置了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;我們取第一IP就好了),但是我們要考慮客戶端偽造頭部的情況,如下示例:
    假設我們在所有代理都加上了proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;然后我們在proxy01機器上本機curl代替win10模擬一個客戶端請求,在proxy01上執行: curl localhost/admin -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: 2.2.2.2'
    1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132|127.0.0.1|101.254.182.6 - - [23/May/2017:11:02:09 +0800] "GET /admin HTTP/1.0" 301 263 "-" "curl/7.15.5 (i386-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" "1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132"

    可以看到,1.1.1.1放到了最前面,所以我們不能夠想當然的去取第一個ip作為客戶端的這是IP.這里127.0.0.1是真實IP.

  5. 雖然X-Forwarded-For可以偽造,但是對我們依然有用,比如我們就從proxy01代理往后截取就行了,這樣就能做到直接忽視偽造得IP.
  6. 引用本配置的X-Forwarded-For和X-Real-IP

    $http_x_forwarded_for

    $http_x_real_ip

 

討論一下X-Real-IP

下面我們看一下有多級代理存在時如何獲取客戶端真實IP.

首先要明確在header里面的 X-Real-IP只是一個變量,后面的設置會覆蓋前面的設置(跟X-Forwarded-For的追加特性區別明顯),所以我們一般只在第一個代理設置proxy_set_header X-Real-IP $remote_addr;就好了,然后再應用端直接引用$http_x_real_ip就行.

1.假如我們只在proxy01設置了 X-Real-IP

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.1|101.254.182.6 - - [23/May/2017:11:23:00 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"

 

2.假如我們只在proxy02設置了X-Real-IP

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:26:22 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"

 

3.假如我們只在proxy03設置了X-Real-IP

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.132|101.254.182.6 - - [23/May/2017:11:27:21 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"

 

4.所有代理都設置X-Real-IP

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.132|101.254.182.6 - - [23/May/2017:11:29:09 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"
 

 

5.強迫症來了,再試一個只設置proxy01,proxy02的看看

192.168.247.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:30:36 +0800] "GET /test/ HTTP/1.0" 200 9 "http://test.proxy.com/test/" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36" "192.168.247.1, 192.168.247.131, 192.168.247.132"

假如有人假冒X-Real-IP呢?

 

6. 在proxy01上執行: curl localhost/admin -H 'X-Forwarded-For: 1.1.1.1' -H 'X-Real-IP: xx.xx.xx.xx'

1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132|192.168.247.131|101.254.182.6 - - [23/May/2017:11:36:02 +0800] "GET /admin HTTP/1.0" 301 263 "-" "curl/7.15.5 (i386-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5" "1.1.1.1, 127.0.0.1, 192.168.247.131, 192.168.247.132"

並沒有影響.

 

Java代碼獲取真實IP(nginx配置成功前提):

 1     /**
 2      * 獲取HTTP用戶真實ip
 3      * @param request
 4      * @return
 5      */
 6     public static String getIpAddr(HttpServletRequest request) {
 7         String ipAddress;
 8         try {
 9             ipAddress = request.getHeader("X-Real-IP"); // 小米集團Nginx
10             log.info("ip: X-Real-IP:{}", ipAddress);
11             if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
12                 ipAddress = request.getHeader("X-Forwarded-For"); // 巴西aws
13                 log.info("ip: X-Forwarded-For:{}", ipAddress);
14             }
15             if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
16                 ipAddress = request.getHeader("x-forwarded-for");// http2必須使用小寫,兼容http2
17                 log.info("ip: x-forwarded-for:{}", ipAddress);
18             }
19             if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
20                 ipAddress = request.getHeader("Proxy-Client-IP"); // 暫時不用
21                 log.info("ip: Proxy-Client-IP:{}", ipAddress);
22             }
23             if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
24                 ipAddress = request.getHeader("WL-Proxy-Client-IP"); // 暫時不用
25                 log.info("ip: WL-Proxy-Client-IP:{}", ipAddress);
26             }
27             if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
28                 ipAddress = request.getRemoteAddr();
29                 if (LOCALHOST.equals(ipAddress)) {
30                     InetAddress inet = null;
31                     try {
32                         inet = InetAddress.getLocalHost();
33                         ipAddress = inet.getHostAddress();
34                     } catch (UnknownHostException e) {
35                         log.error("getLocalHost error", e);
36                     }
37                 }
38                 log.info("ip: localhost:{}", ipAddress);
39             }
40             // 對於通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割
41             // "***.***.***.***".length()
42             if (ipAddress != null && ipAddress.length() > 15) {
43                 if (ipAddress.indexOf(SEPARATOR) > 0) {
44                     ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
45                 }
46             }
47         } catch (Exception e) {
48             ipAddress = "";
49         }
50         return ipAddress;
51     }

 


免責聲明!

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



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