網(wǎng)關(guān)其實好理解,說簡單一些的就路由器吧,因為 IPv4 的地址空間是有限的,所以就有了局域網(wǎng)共用一個公網(wǎng) IP 的事實。這在一個集體里面很容易出現(xiàn),例如家庭、學(xué)校,如果我們不加分辨得直接就使用 IP 來記錄或者屏蔽,那么很容易出問題,如果將這個問題再擴大化一點,那就是移動端,因為我們知道移動端都是通過連接信號塔進行數(shù)據(jù)通信的,那么對于一個范圍內(nèi)的同一運營商來說,IP 地址就很可能是一樣的,這是移動開發(fā)中一個很關(guān)鍵的點;還有就是代理,很多公司對于網(wǎng)絡(luò)都是封鎖得很嚴(yán)厲的,所以所有的對外流量都通過一個代理交流,這也就導(dǎo)致了很多情況下都是同一個代理出來的都是一個 IP,這也是一個非常重要的問題,很容易一棍子打死一船人。
OK,閑話扯完了,回到主題,在后端程序中,一般客戶端/前端的流量都不會直接就打到后端的應(yīng)用上,正常最少都會加一層反向代理,稍復(fù)雜一些的還會有負載均衡啥的,這也給我們提取客戶端 IP 帶來了很大的麻煩,所以我這里就以 Nginx 為例,說說如何更好得獲取正確的 IP 值。
下面下來一段我用了好多年的 Nginx 配置,夸張點說就祖?zhèn)鞯陌桑?/p>
location /server/ { proxy_pass http://127.0.0.1:3999; 后臺服務(wù)地址 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $Host; proxy_set_header X-Forwarded-Proto $scheme; }
這里會出現(xiàn)了好幾個和 IP 有關(guān)的字段,這也是獲取 IP 的關(guān)鍵,對于這些字段如果我們細致得了解了它的來源和原理之后,那么獲取相對準(zhǔn)確的 IP 也就沒那么困難了,下面就一一進行介紹:
Remote Addr
remote_addr 這個字段不是 http 里面的概念,其實是 tcp 的概念,表示的是當(dāng)前連接的對端的地址,也就是說:
- 如果在瀏覽器和 Nginx 之間不存在其他代理,那么這個字段就是真實的 IP
- 但是,一旦瀏覽器和 Nginx 之間存在代理,那么這個字段的值就是最后一個代理的地址
X-Real-IP
正如配置中所示,HTTP 中其實不存在這個 Header,但是在 Nginx 中習(xí)慣于用來標(biāo)識用戶的真實地址,至于是否真的是客戶端的地址,看前面的 remote_addr 的解釋我們就清楚了。
X-Forwarded-For
這個就有意思了,X-Forwarded-For 表示在客戶端訪問 Nginx 的過程中如果需要經(jīng)過 HTTP 代理或者負載均衡服務(wù)器,可以被用來獲取最初發(fā)起請求的客戶端的 IP 地址,這個消息首部成為事實上的標(biāo)準(zhǔn)。怎么說,其實就是一個 HTTP 請求從瀏覽器發(fā)出,每經(jīng)過一個 HTTP 代理或者負載均衡,都會在這個 Header 里面添加一條記錄(當(dāng)然,這是規(guī)定,你不遵守我也沒辦法),所以對于一個請求來說,X-Forwarded-For Header 的值列表里面的第一個值應(yīng)該就是客戶端的地址,及時經(jīng)過了 N 多的代理和負載均衡。
但是,這畢竟不是真正的標(biāo)準(zhǔn),所以我們不能期望 100% 一定有這個,但是根據(jù)我的經(jīng)驗,對于一些比較成熟的反向代理軟件 例如 Nginx/Squid 都是有的,所以大多數(shù)情況下都可以通過這個字段獲取到真實值。
X-Forwarded-Host
好吧,這個 Header 是亂入的,它和客戶端的 IP 沒啥關(guān)系,它其實是標(biāo)識客戶端發(fā)起請求時的 Host 的地址,我們可以通過這個 Header 來獲取客戶端是訪問的哪個 Host 進來的。
結(jié)論
所以通過上面的介紹,我們知道,其實就只有兩個東西,分別是 remote_addr 和 X-Forwarded-For,如果中間存在不可控的代理,那么我們應(yīng)該優(yōu)先通過 X-Forwarded-For 的第一個值來獲取客戶端真實 IP;如果中間的代理都是可控的,那么我們優(yōu)先通過 remote_addr 來獲取客戶端真實的 IP,而且這個 IP 是不可偽造的。