关于使用代理后,后端获取客户端IP中遇到的问题(比如.NetCore、Vue、Nginx)
首先,我们有多种方式获取本地IP地址,比如:.net core获取本地Ip地址的方法 ,这种方式与项目类型无关。
如果后端项目是一个web项目,我们还可以通过HttpContext来获取后端项目所在服务器的本地IP,而且还能获取客户端的IP地址:
var connection = HttpContext.Connection; //客户端IP地址(v4和v6) var remoteIpAddressIPv4 = connection.RemoteIpAddress?.MapToIPv4()?.ToString(); var remoteIpAddressIPv6 = connection.RemoteIpAddress?.MapToIPv6()?.ToString(); //服务端本地IP地址(v4和v6) var localIpAddressIPv4 = connection.LocalIpAddress?.MapToIPv4()?.ToString(); var localIpAddressIPv6 = connection.LocalIpAddress?.MapToIPv6()?.ToString();
但是现在的web项目应该很少有直接面向客户端使用的吧,都是中间或多或少有几个网关或者代理,比如使用nginx:
location / { proxy_pass http://localhost:5000; }
或者前端Vue开发时使用了使用http-proxy-middleware做代理:
devServer: { proxy: { '/api': { target: process.env.VUE_APP_API_BASE_URL, ws: false, changeOrigin: true, pathRewrite: { '^/api': '' // 需要rewrite的, } } } }
那么问题来了,一旦客户端的请求被代理了,那么上面获取获取客户端IP地址的方式就不准确了,因为请求被代理转发了,这样它获取的就是代理端的IP,而不是客户端的IP,那么怎么解决这个问题呢?
这就要说到两个特殊的请求头了:X-Real-IP 与 X-Forwarded-For
X-Real-IP:表示真实的IP,但是实际上,它表示多级代理中最后一个代理的IP,可惜的是,它只是被一些组织机构认可,貌似并没有一个标准的规范。
X-Forwarded-For:表示请求从客户端发起后所经过的所有代理的IP地址,这些IP地址使用英文逗号+空格(, )进行分隔,其中第一个就是客户端的IP地址,形如:
X-Forwarded-For: 192.168.128.111, 192.168.128.112, 192.168.128.113
那么解决这个问题的关键就是在代理转发请求时,我们需要给转发的请求加上这个X-Forwarded-For请求头!X-Real-IP可以添加,也可以不添加,只是后端读取不一样。
如果使用的是Nginx:
location / { proxy_pass http://localhost:5000; proxy_http_version 1.1; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 如果是代理websocket,还需要下面两个 # proxy_set_header Upgrade $http_upgrade; # proxy_set_header Connection "upgrade"; }
$remote_addr表示上一级代理的IP,如果上一级是客户端,那么它就表示客户端的IP,$proxy_add_x_forwarded_for表示将$remote_addr追加到上一级代理的X-Forwarded-For请求头中,并将追加后的内容作为X-Forwarded-For请求头转发到下一级,这样客户端的IP就一级一级的转发到了服务端
如果是Vue开发过程中使用了http-proxy-middleware做代理:
devServer: { proxy: { '/api': { //如果是API接口 target: process.env.VUE_APP_API_BASE_URL, ws: false, changeOrigin: true, pathRewrite: { '^/api': '' // 需要rewrite的, }, onProxyReq(proxyReq, req, res) { //req中默认是携带有x-forwarded-for请求头的,只需添加X-Real-IP请求头 req.headers['X-Real-IP'] = req.ip } }, '/socket': { //如果是websocket target: process.env.VUE_APP_SOCKET_BASE_URL, ws: true, changeOrigin: true, onProxyReqWs(proxyReq, req, socket, options, head) { //req中默认是携带有x-forwarded-for请求头的,只需添加X-Real-IP请求头 req.headers['X-Real-IP'] = req.ip } } } }
这里onProxyReq是代理普通http接口前会触发的事件,onProxyReqWs则是代理websocket前会触发的事件
接着后端获取IP可以从下面的规则中获取:
string ip = HttpContext.Request.Headers["X-Real-IP"].ToString(); if (string.IsNullOrEmpty(ip)) { string ips = HttpContext.Request.Headers["X-Forwarded-For"].ToString(); ip = ips.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(); if (string.IsNullOrEmpty(ip)) { ip = HttpContext.Connection.RemoteIpAddress?.MapToIPv4()?.ToString(); } }