在平時(shí)我們開(kāi)發(fā)后端程序的過(guò)程中,應(yīng)該多多少少都會(huì)碰到記錄客戶(hù)端 IP 的場(chǎng)景,例如我之前寫(xiě)過(guò)的 APP 用戶(hù)的一個(gè)審計(jì)功能,就需要獲取用戶(hù)的 IP 地址;還有廣告系統(tǒng)里面,也是需要獲取用戶(hù)的 IP 地址,有時(shí)這個(gè) IP 地址會(huì)被用來(lái)標(biāo)識(shí)用戶(hù)的,因此需要比較準(zhǔn)確得獲取到用戶(hù)的地址。當(dāng)然,在開(kāi)始本文的內(nèi)容之前還是有必要強(qiáng)調(diào)一下我們現(xiàn)在的網(wǎng)絡(luò)大環(huán)境的,在使用 IP 的時(shí)候,我們一定要記住有兩個(gè)東西很關(guān)鍵,一個(gè)是網(wǎng)關(guān),一個(gè)是代理。

網(wǎng)關(guān)其實(shí)好理解,說(shuō)簡(jiǎn)單一些的就路由器吧,因?yàn)?IPv4 的地址空間是有限的,所以就有了局域網(wǎng)共用一個(gè)公網(wǎng) IP 的事實(shí)。這在一個(gè)集體里面很容易出現(xiàn),例如家庭、學(xué)校,如果我們不加分辨得直接就使用 IP 來(lái)記錄或者屏蔽,那么很容易出問(wèn)題,如果將這個(gè)問(wèn)題再擴(kuò)大化一點(diǎn),那就是移動(dòng)端,因?yàn)槲覀冎酪苿?dòng)端都是通過(guò)連接信號(hào)塔進(jìn)行數(shù)據(jù)通信的,那么對(duì)于一個(gè)范圍內(nèi)的同一運(yùn)營(yíng)商來(lái)說(shuō),IP 地址就很可能是一樣的,這是移動(dòng)開(kāi)發(fā)中一個(gè)很關(guān)鍵的點(diǎn);還有就是代理,很多公司對(duì)于網(wǎng)絡(luò)都是封鎖得很?chē)?yán)厲的,所以所有的對(duì)外流量都通過(guò)一個(gè)代理交流,這也就導(dǎo)致了很多情況下都是同一個(gè)代理出來(lái)的都是一個(gè) IP,這也是一個(gè)非常重要的問(wèn)題,很容易一棍子打死一船人。

OK,閑話(huà)扯完了,回到主題,在后端程序中,一般客戶(hù)端/前端的流量都不會(huì)直接就打到后端的應(yīng)用上,正常最少都會(huì)加一層反向代理,稍復(fù)雜一些的還會(huì)有負(fù)載均衡啥的,這也給我們提取客戶(hù)端 IP 帶來(lái)了很大的麻煩,所以我這里就以 Nginx 為例,說(shuō)說(shuō)如何更好得獲取正確的 IP 值。

下面下來(lái)一段我用了好多年的 Nginx 配置,夸張點(diǎn)說(shuō)就祖?zhèn)鞯陌桑?/p>

截圖

location /server/ {
        proxy_pass  http://127.0.0.1:3999;   后臺(tái)服務(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;
}

這里會(huì)出現(xiàn)了好幾個(gè)和 IP 有關(guān)的字段,這也是獲取 IP 的關(guān)鍵,對(duì)于這些字段如果我們細(xì)致得了解了它的來(lái)源和原理之后,那么獲取相對(duì)準(zhǔn)確的 IP 也就沒(méi)那么困難了,下面就一一進(jìn)行介紹:

Remote Addr

remote_addr 這個(gè)字段不是 http 里面的概念,其實(shí)是 tcp 的概念,表示的是當(dāng)前連接的對(duì)端的地址,也就是說(shuō):

  • 如果在瀏覽器和 Nginx 之間不存在其他代理,那么這個(gè)字段就是真實(shí)的 IP
  • 但是,一旦瀏覽器和 Nginx 之間存在代理,那么這個(gè)字段的值就是最后一個(gè)代理的地址

X-Real-IP

正如配置中所示,HTTP 中其實(shí)不存在這個(gè) Header,但是在 Nginx 中習(xí)慣于用來(lái)標(biāo)識(shí)用戶(hù)的真實(shí)地址,至于是否真的是客戶(hù)端的地址,看前面的 remote_addr 的解釋我們就清楚了。

X-Forwarded-For

這個(gè)就有意思了,X-Forwarded-For 表示在客戶(hù)端訪(fǎng)問(wèn) Nginx 的過(guò)程中如果需要經(jīng)過(guò) HTTP 代理或者負(fù)載均衡服務(wù)器,可以被用來(lái)獲取最初發(fā)起請(qǐng)求的客戶(hù)端的 IP 地址,這個(gè)消息首部成為事實(shí)上的標(biāo)準(zhǔn)。怎么說(shuō),其實(shí)就是一個(gè) HTTP 請(qǐng)求從瀏覽器發(fā)出,每經(jīng)過(guò)一個(gè) HTTP 代理或者負(fù)載均衡,都會(huì)在這個(gè) Header 里面添加一條記錄(當(dāng)然,這是規(guī)定,你不遵守我也沒(méi)辦法),所以對(duì)于一個(gè)請(qǐng)求來(lái)說(shuō),X-Forwarded-For Header 的值列表里面的第一個(gè)值應(yīng)該就是客戶(hù)端的地址,及時(shí)經(jīng)過(guò)了 N 多的代理和負(fù)載均衡。

但是,這畢竟不是真正的標(biāo)準(zhǔn),所以我們不能期望 100% 一定有這個(gè),但是根據(jù)我的經(jīng)驗(yàn),對(duì)于一些比較成熟的反向代理軟件 例如 Nginx/Squid 都是有的,所以大多數(shù)情況下都可以通過(guò)這個(gè)字段獲取到真實(shí)值。

X-Forwarded-Host

好吧,這個(gè) Header 是亂入的,它和客戶(hù)端的 IP 沒(méi)啥關(guān)系,它其實(shí)是標(biāo)識(shí)客戶(hù)端發(fā)起請(qǐng)求時(shí)的 Host 的地址,我們可以通過(guò)這個(gè) Header 來(lái)獲取客戶(hù)端是訪(fǎng)問(wèn)的哪個(gè) Host 進(jìn)來(lái)的。

結(jié)論

所以通過(guò)上面的介紹,我們知道,其實(shí)就只有兩個(gè)東西,分別是 remote_addr 和 X-Forwarded-For,如果中間存在不可控的代理,那么我們應(yīng)該優(yōu)先通過(guò) X-Forwarded-For 的第一個(gè)值來(lái)獲取客戶(hù)端真實(shí) IP;如果中間的代理都是可控的,那么我們優(yōu)先通過(guò) remote_addr 來(lái)獲取客戶(hù)端真實(shí)的 IP,而且這個(gè) IP 是不可偽造的。