express獲取client_ip
req.ip // 獲取客戶端ip
req.ips // 獲取請求經過的客戶端與代理服務器的Ip列表
查看源碼
定義獲取ip的入口,
// 源碼 request.js
defineGetter(req, 'ip', function ip(){
var trust = this.app.get('trust proxy fn');
let add = proxyaddr(this, trust);
return add
});
defineGetter(req, 'ips', function ips() {
var trust = this.app.get('trust proxy fn');
var addrs = proxyaddr.all(this, trust);
addrs.reverse().pop()
return addrs
});
defineGetter
是 Object.defineProperty
的封裝,所以我們能在req對象上獲取到ip。
trust proxy 的意義
trust proxy :當用戶設置trust proxy的值代表用戶認為這些值是代理服務器。那么在獲取真實客戶端ip的時候就需要進行將這些代理ip過濾掉,
而this.app.get('trust proxy fn')會生成對應的過濾器,這里的過濾器就是函數,類似於數組的filter的callback。
如果用戶沒有設置'trust proxy'
,this.app.get('trust proxy fn')
的返回值如下:
function trustNone () {
return false
}
如果用戶沒有設置'trust proxy'
,返回值類似如下:
function trust (addr) {
if (!isip(addr)) return false
var ip = parseip(addr)
var kind = ip.kind()
if (kind !== subnetkind) {
if (subnetisipv4 && !ip.isIPv4MappedAddress()) {
// Incompatible IP addresses
return false
}
// Convert IP to match subnet IP kind
ip = subnetisipv4
? ip.toIPv4Address()
: ip.toIPv4MappedAddress()
}
return ip.match(subnetip, subnetrange)
}
這里牽扯到express中的get set方法,源碼一並列出:
// 源碼 application.js
// 初始化express的是后運行,默認設置trust proxy 為false,
this.set('trust proxy', false);
// 定義get方法
methods.forEach(function(method){
app[method] = function(path){
if (method === 'get' && arguments.length === 1) {
return this.set(path); // 調用get運行到這里,相當於調用了set方法
}
......
};
});
// 定義set方法
app.set = function set(setting, val) {
if (arguments.length === 1) {
return this.settings[setting]; //調用set方法運行到這里,相當於返回了settings這個對象中的某個配置
}
.....
this.settings[setting] = val;
......
case 'trust proxy':
this.set('trust proxy fn', compileTrust(val));
....
};
經過以上代碼的運行,
則兩個方法的作用是用來過濾
proxyaddr
這是npm包proxy-addr
,express文檔中提到的linklocal,loopback,uniquelocal
是在這里定義的,上面提到的ip過濾方法也是這個包來生成。
由函數forward來處理獲取ip,事實上使用x-forwarded-for 和 req.connection.remoteAddress的類獲取ip。
function forwarded (req) {
....
var proxyAddrs = parse(req.headers['x-forwarded-for'] || '')
var socketAddr = req.connection.remoteAddress
var addrs = [socketAddr].concat(proxyAddrs)
return addrs
}
實例
var express = require('express')
var app = express()
app.set('trust proxy','127.0.0.1')
app.use(function (req,res) {
if(req.url === '/favicon.ico')return
res.send({
ip:req.ip,
ips:req.ips
})
console.log(app.get('trust proxy'));
})
app.listen('9090','0.0.0.0')
同時nginx設置代理:
server {
listen 8062;
server_name localhost;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://127.0.0.1:9090;
}
}
此時只有一台代理服務,轉發到express的x-forwarded-for為"192.168.1.105",而代理服務器nginx的ip為127.0.0.1,經過forwarded方法處理之后是待過濾的ip數組是 ["127.0.0.1","192.168.1.105"]。
- 在另一台電腦(內網ip192.168.1.105)訪問192.168.1.102(內網本機IP):8062 ,返回:
{"ip":"192.168.1.105","ips":["192.168.1.105"]}
解釋:因為trust proxy和nginx的ip一致,所以過濾掉了nginx的ip,剩下的ip中的第一個被認為是客戶端ip;因為ips本身是就是客戶端ip和代理ip的集合,這里代理nginx被過濾掉了,最后和客戶端一樣。
- 去掉
app.set('trust proxy','127.0.0.1')
的結果:{"ip":"127.0.0.1","ips":[]}
解釋:沒有設置trust proxy,也就是認為沒有代理,獲取的ip為代理服務器的ip。在此情況下ips為為空數組。
- 換成
app.set('trust proxy','127.0.0.100/28')
的結果:{"ip":"127.0.0.1","ips":[]}
解釋:上面的28為網段的CIDR表示法,CIDR是什么呢?有點像正則匹配一樣,舉個例子:127.0.0.1沒有落在127.0.0.100/28所便是的網段,所以express在使用過濾器的時候沒有匹配到,最后返回了第一個ip。沒有匹配到代表ips也為空數組;
- 換成
app.set('trust proxy','127.0.0.100/18')
的結果:{"ip":"192.168.1.105","ips":["192.168.1.105"]}
解釋:127.0.0.1 落在了127.0.0.100/18表示的網段,和上面的相反,過濾掉127.0.0.1。
總結
程序的邏輯如下:
- 如果沒有設置
trust proxy
或者設置trust proxy
為false,獲取的是ip就是req.connection.remoteAddress
; - 如果設置了
trust proxy
,express會生成對應的過濾函數,過濾[代理ip,x-forwarded-for中的ip],返回結果數組的第一個ip; - ips的處理邏輯類似;