反向代理與 Real-IP 和 X-Forwarded-For

 

開篇語:開濤新作《億級流量網站架構核心技術》出版計劃公佈以來,博文視點遭受到一波又一波讀者詢問面世時間的DDos攻擊。面對億級流量的熱情,感激之餘,我們也很慶幸——這部作品質量的確過硬,不會辜負擁躉厚望,更有開濤的高度負責和體貼周到加持,讓她絕對物超所值、長久流芳。下面,看一篇來自這位技術暖男的售前支持。 
——本書策劃編輯 俠少

  本文作者張開濤。爲保障《億級流量網站架構核心技術》一書內容的連續性,有些需要讀者瞭解的內容,或者書的補充和引申內容,會通過二維碼嵌入的方式引導讀者閱讀學習。大家可以關注作者公衆號“開濤的博客”,並從菜單欄“我的新書”中查閱相關內容。

  本文是「4.4 接入層限流」節中的「按照IP限制併發連接數配置示例」部分需要了解的內容。 
  當我們訪問互聯網上的服務時,大多數時,客戶端並不是直接訪問到服務端的,而是客戶端首先請求到反向代理,反向代理再轉發到服務端實現服務訪問,通過反向代理實現路由/負載均衡等策略。這樣一來在服務端拿到的客戶端IP將是反向代理IP,而不是真實客戶端IP,因此需要一種辦法來獲取到真實客戶端IP。 
  如下圖所示,客戶端通過Nginx Proxy1 和 Nginx Proxy2 兩層反向代理才訪問到具體服務Nginx Backend(或如Tomcat服務)。那Nginx Backend如何才能拿到真實客戶端IP呢? 
圖片描述

  接下來我們來看看如何才能獲取到客戶端真實IP。

場景1

  場景1是很簡單的場景,Nginx Proxy直接把請求往後轉發,沒有做任何處理。

Nginx Proxy 192.168.107.107 nginx.conf
location /test {
    proxy_pass http://192.168.107.112:8080;
} 192.168.107.112 nginx.conf
location /test {
    proxy_pass http://192.168.107.114:8080;
}
Nginx Proxy就是簡單的把請求往後轉發。
Nginx Backend 192.168.107.114 nginx.conf
location /test {
    default_type text/html;
    charset gbk; echo "$remote_addr || $http_x_forwarded_for";
}

  Nginx Backend輸出客戶端IP($remote_addr)和X-Forwarded-For請求頭($http_x_forwarded_for),當訪問服務時輸出結果如下所示:

192.168.107.112 ||

分析

1.$remote_addr代表客戶端IP,當前配置的輸出結果爲最後一個代理服務器的IP,並不是真實客戶端IP; 
2.在沒有特殊配置情況下,X-Forwarded-For請求頭不會自動添加到請求頭中,即Nginx Backend的$http_x_forwarded_for輸出爲空。

場景2

  場景2通過添加X-Real-IP和X-Forwarded-For捕獲客戶端真實IP。

Nginx Proxy 192.168.107.107 nginx.conf
location /test {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://192.168.107.112:8080;
} 192.168.107.112 nginx.conf
location /test {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://192.168.107.114:8080;
}
Nginx Backend 192.168.107.114 nginx.conf
location /test {
    default_type text/html;
    charset gbk; echo "$remote_addr ||$http_x_real_ip ||$http_x_forwarded_for";
}

  當訪問服務時,輸出結果爲:

192.168.107.112 || 192.168.162.16 || 192.168.162.16, 192.168.107.107

分析

1.在離用戶最近的反向代理NginxProxy 1,通過“proxy_set_header X-Real-IP $remote_addr”把真實客戶端IP寫入到請求頭X-Real-IP,在NginxBackend輸出$http_x_real_ip獲取到了真實客戶端IP;而Nginx Backend的“$remote_addr”輸出爲最後一個反向代理的IP; 
2.“proxy_set_headerX-Forwarded-For $proxy_add_x_forwarded_for”的是把請求頭中的X-Forwarded-For與$remote_addr用逗號合起來,如果請求頭中沒有X-Forwarded-For則$proxy_add_x_forwarded_for爲$remote_addr。 
  X-Forwarded-For代表了客戶端IP,反向代理如Nginx通過$proxy_add_x_forwarded_for添加此項,X-Forwarded-For的格式爲X-Forwarded-For:real client ip, proxy ip 1, proxy ip N,每經過一個反向代理就在請求頭X-Forwarded-For後追加反向代理IP。 
  到此我們可以使用請求頭X-Real-IP和X-Forwarded-For來獲取客戶端IP及客戶端到服務端經過的反向代理IP了。這種方式還是很麻煩,$remote_addr並不是真實客戶端IP。

場景3

  爲了更方便的獲取真實客戶端IP,可以使用nginx http_realip_module模塊解決,在安裝nginx時通過–with-http_realip_module安裝該模塊。NginxProxy配置和場景2一樣。

Nginx Backend 192.168.107.114 nginx.conf
real_ip_header X-Forwarded-For; set_real_ip_from 192.168.0.0/16; 
real_ip_recursive on; 

location /test {
    default_type text/html;
    charset gbk; echo "$remote_addr || $http_x_real_ip ||$http_x_forwarded_for";
}

  當訪問服務時,輸出結果爲:

192.168.162.16 || 192.168.162.16 || 192.168.162.16, 192.168.107.107

分析

1.X-Real-IP和X-Forwarded-For和場景2一樣; 
2.使用realip模塊後,$remote_addr輸出結果爲真實客戶端IP,可以使用$realip_remote_addr獲取最後一個反向代理的IP; 
3.real_ip_headerX-Forwarded-For:告知Nginx真實客戶端IP從哪個請求頭獲取,如X-Forwarded-For; 
4.set_real_ip_from192.168.0.0/16:告知Nginx哪些是反向代理IP,即排除後剩下的就是真實客戶端IP,支持配置具體IP地址、CIDR地址; 
5.real_ip_recursive on:是否遞歸解析,當real_ip_recursive配置爲off時,Nginx會把real_ip_header指定的請求頭中的最後一個IP作爲真實客戶端IP;當real_ip_recursive配置爲on時,Nginx會遞歸解析real_ip_header指定的請求頭,最後一個不匹配set_real_ip_from的IP作爲真實客戶端IP。 
  如果配置“real_ip_recursive off; ”,則輸出結果爲:

192.168.107.107 || 192.168.162.16 ||192.168.162.16, 192.168.107.107

總結

1.通過“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for” 把從真實客戶端IP和反向代理IP通過逗號分隔,添加到請求頭中; 
2.可以在第一個反向代理上配置“proxy_set_header X-Real-IP $remote_addr” 獲取真實客戶端IP; 
3.配合realip模塊從X-Forwarded-For也可以獲取到真實客戶端IP。 
  在整個反向代理鏈條的第一個反向代理可以不配置“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for”,否則客戶端可以僞造X-Forwarded-For從而僞造客戶端真實IP,如果服務端使用X-Forwarded-For第一個IP作爲真實客戶端IP,則就出問題了。如果通過配置X-Real-IP請求頭或者配合realip模塊也不會出現該問題。如果自己解析X-Forwarded-For的話,記得使用realip算法解析,而不是取第一個。 
當我們進行限流時一定注意限制的是真實客戶端IP,而不是反向代理IP,我遇到過很多同事在這塊出問題的。

 


想及時獲得更多精彩文章,可在微信中搜索“博文視點”或者掃描下方二維碼並關注。
圖片描述

發佈了187 篇原創文章 · 獲贊 0 · 訪問量 8386
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章