X-Forwarded-For 是一個 HTTP 擴展頭部,主要是爲了讓 Web 服務器獲取訪問用戶的真實 IP 地址(其實這個真實未必是真實的,後面會說到)。nginx
那爲何 Web 服務器只有經過 X-Forwarded-For 頭才能獲取真實的 IP?
這裏用 PHP 語言來講明,不明白原理的開發者爲了獲取客戶 IP,會使用 $_SERVER['REMOTE_ADDR'] 變量,這個服務器變量表示和 Web 服務器握手的 IP 是什麼(這個不能僞造)。
可是不少用戶都經過代理來訪問服務器的,那麼假如使用該全局變量,PHP獲取到的 IP 就是代理服務器的 IP(不是用戶的)。安全
可能不少人看的暈乎乎的,那麼看看一個請求可能通過的路徑:服務器
客戶端=>(正向代理=>透明代理=>服務器反向代理=>)Web服務器
其中正向代理、透明代理、服務器反向代理這三個環節並不必定存在。負載均衡
- 什麼是正向代理呢,不少企業會在本身的出口網關上設置代理(主要是爲了加速和節省流量)。
- 透明代理多是用戶本身設置的代理(好比爲了FQ,這樣也繞開了公司的正向代理)。
- 服務器反向代理是部署在 Web 服務器前面的,主要緣由是爲了負載均衡和安全考慮。
如今假設幾種狀況:post
- 假如客戶端直接鏈接 Web 服務器(假設 Web 服務器有公網地址),則 $_SERVER['REMOTE_ADDR'] 獲取到的是客戶端的真實 IP 。
- 假設 Web 服務器前部署了反向代理(好比 Nginx),則 $_SERVER['REMOTE_ADDR'] 獲取到的是反向代理設備的 IP(Nginx)。
- 假設客戶端經過正向代理直接鏈接 Web 服務器(假設 Web 服務器有公網地址),則 $_SERVER['REMOTE_ADDR'] 獲取到的正向代理設備的 IP 。
其實這裏的知識點不少,記住一點就好了,$_SERVER['REMOTE_ADDR'] 獲取到的 IP 是 Web 服務器 TCP 鏈接的 IP(這個不能僞造,通常 Web 服務器也不會修改這個頭)。網站
X-Forwarded-For
從上面你們也看出來了,由於有了各類代理,纔會致使 REMOTE_ADDR 這個全局變量產生了必定的歧義,爲了讓 Web 服務器獲取到真實的客戶端 IP,X-Forwarded-For 出現了,這個協議頭也是由 Squid 起草的(Squid 應該是最先的代理軟件之一)。ui
這個協議頭的格式:spa
X-Forwarded-For: client, proxy1, proxy2
client 表示用戶的真實 IP,每通過一次代理服務器,代理服務器會在這個頭增長用戶的 IP(有點拗口)。
注意最後一個代理服務器請求 Web 服務器的時候是不會將本身的 IP 附加到 X-Forwarded-For 頭上的,最後一個代理服務器的 IP 地址應該經過$_SERVER['REMOTE_ADDR']獲取。代理
舉個例子:
用戶的 IP 爲(A),分別通過兩個代理服務器(B,C),最後到達 Web 服務器,那麼Web 服務器接收到的 X-Forwarded-For 就是 A,B。日誌
那麼 PHP 如何獲取真實客戶端 IP 呢?
$ip = isset($_SERVER['HTTP_X_FORWARDED_FOR']) ? trim($_SERVER['HTTP_X_FORWARDED_FOR']) : ''; if (!$ip) { $ip = isset($_SERVER['REMOTE_ADDR']) ? trim($_SERVER['REMOTE_ADDR']) : ''; } $a = explode('|', str_replace(',', '|', $ip)); $ip = trim($a[0]);
這裏預先說明下,假設這兩個代理服務器都是好的代理服務器,沒有僞造 HTTP_X_FORWARDED_FOR。
配置反向代理
上面一直在說代理,你們可能以爲這到底有啥用?不一樣類型的代理有不一樣的目的,對於正向代理來講主要是爲了加速而且讓局域網的用戶有一個真實的 IP 地址,而透明代理則主要是爲了一些其餘的目的(好比就是不想讓別人知道個人 IP),而反向代理主要是企業內部安全和負載均衡考慮,這裏主要說下如何配置反向代理。
如今只要是具有必定規模的網站(Web 服務器大於 1 臺),爲了安全和負載均衡考慮都會在 Web 服務器前面部署反向代理,反向代理有 HAproxy,Nginx,Apache 等等。
這裏經過 Nginx 來部署反向代理:
proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
簡單的解釋下:
- X-Forwarded-For 表示 Nginx 接收到的頭,原樣的轉發過來(假如不轉發,Web 服務器就不能獲取這個頭)。
- X-Real-IP,這是一個內部協議頭(就是反向代理服務器和 Web 服務器約定的),這個頭表示鏈接反向代理服務器的 IP 地址(這個地址不能僞造),其實我的以爲爲了讓 PHP 代碼保持無二義性,不該該這樣設置,能夠修改成
proxy_set_header REMOTE_ADDR $remote_addr;
Apache WEB 服務器的 Access 日誌如何獲取 X-Forwarded-For 頭
其實寫這篇文章主要是由於本身在 Apache Web 服務器上獲取不到 X-Forwarded-For(上層的負載均衡設備肯定傳遞了),搜索了下(在 Apache 官方文檔並無找到解決方案),解決以下:
LogFormat "%{X-Forwarded-For}i %a %h %A %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
X-Forwarded-For 安全性
那麼不少同窗會說,經過 X-Forwarded-For 就能獲取到用戶的真實 IP,是否是萬事大吉了,對於 Web 服務器來講,安全有兩個緯度,第一個緯度是 REMOTE_ADDR 這個頭,這個頭不能僞造。第二個緯度就是 X-Forwarded-For,可是這個頭是能夠僞造的。
那麼誰在僞造呢?,咱們分別看下:
正向代理通常是公司加速使用的,假如沒有特殊的目的,不該該傳遞 X-Forwarded-For 頭,由於它的上層鏈接是內部 IP,不該該暴露出去,固然它也能夠透明的傳遞這個頭的值(而這個值用戶能夠僞造)。
透明代理,這個多是用戶本身搭建的(好比FQ),並且在一個用戶的請求中,可能有多個透明代理,這時候透明代理就抓瞎了,爲了讓本身儘可能的正確,也會透明的傳遞這個頭的值(而這個值用戶能夠僞造),固然一些不法企業或者人員,爲了一些目的,會改下這個頭的值(好比來自世界各地的 IP 地址)。
反向代理,Web 服務器前的反向代理服務器是不會僞造的(同一個公司的),通常會原樣傳遞這個頭的值。
那麼對應用程序來講,既然這個值不能徹底相信,該怎麼辦呢?這取決於應用的性質:
假如提供的服務可能就是一些非機密服務,也不須要知道用戶的真實 IP,那麼建議應用程序或者 Web 服務器對 REMOTE_ADDR 作一些限制,好比進行限速等等,也能夠放行一些白名單的代理 IP,可是這些白名單 IP 就太難衡量了。
假設你的服務很重要,好比抽獎(一個 IP 只能一次抽獎),這時候你可能想經過 X-Forwarded-For 來獲取用戶的真實 IP(假如使用 REMOTE_ADDR 則會誤殺一片),可是因爲 X-Forwarded-For 可能會僞造,因此其實並無什麼好的辦法,只能在應用層進行處理了。