关于使用nginx反向代理的真实请求IP问题的解决

时间:2024-3-29    作者:fanlepian    分类:

前言

前段时间我自己尝试做一个使用nodejs的网站demo,使用了自己家里的废旧笔记本作为服务端,因为涉及到家宽不给开80端口的原因,我选择使用虚拟主机然后反向代理我的网站demo。可是在我反向代理的时候遇到一个问题,反向代理请求用的IP是虚拟主机自己的IP,并不是访问者的IP。

这样就很不方便我们后期的运维,毕竟使用反向代理的目的一是为了让他能够通过80端口访问出去,二是作为一个防护作用,不让自己的家宽暴露出去,所以大多数用户都是要通过虚拟主机来访问我的代码的。如果没有吗好的解决办法就后面就很难运维了。

nginx的反向代理配置


nginx的反向代理在配置文件里是这个样子的,使用了proxy_pass来转到我的家宽上面。这样子做就是完全使用虚拟主机进行访问了,可是这样的配置文件还并不是完整的

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
  1. proxy_set_header Host $host; 里面的$host代表着请求时发送给服务端的名称,这里面填入的是nginx中的内置变量,变量的含义就是proxy_pass中所代理网站的名称,一般不用修改
  2. proxy_set_header X-Real-IP $remote_addr; 这条指令将客户端的原始IP地址设置为X-Real-IP头部发送给后端服务器。$remote_addr是Nginx内置变量,它代表了客户端的IP地址。
  3. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 这条指令将X-Forwarded-For头部设置为Nginx代理添加的值。$proxy_add_x_forwarded_for是Nginx内置变量,它代表了使用这个代理的客户端的IP地址,这个是必不可少的
  4. proxy_set_header REMOTE-HOST $remote_addr; 这条指令将客户端的IP地址设置为REMOTE-HOST头部发送给后端服务器。这里使用的是$remote_addr变量,其作用和上面解释的一样。不过,通常X-Forwarded-For头部更常用,所以一般没什么用,但是有一些后端服务端也是需要这样的请求头参数的,最好还是加上
  5. proxy_set_header Upgrade $http_upgrade; 这条指令将客户端的Upgrade头部的值设置为后端服务器请求的头部值。$http_upgrade是Nginx内置变量,它代表了客户端在请求中发送的Upgrade头部的值,通常用于HTTP到WebSocket的协议升级,为了确保ws协议正常工作,最好还是加上他
  6. proxy_set_header Connection $connection_upgrade; 这条指令将客户端的Connection头部的值设置为后端服务器请求的头部值。$connection_upgrade是Nginx内置变量,它代表了客户端在请求中发送的Connection头部的值,通常与WebSocket协议升级一起使用。

服务端的设置

只是在反向代理配置中添加了上面的几行代码,也是没用的,重要的也是服务端的,我先用nodejs写出一个打印请求头的示例,来分析一下整个请求头包含的内容

const http = require('http');
const server = http.createServer((req, res) => {
  // 打印请求头
  console.log(JSON.stringify(req.headers));
  // 设置响应状态码和内容类型
  res.writeHead(200, {'Content-Type': 'text/plain'});
  // 发送响应内容
  res.end('DONE!');
});
// 监听3000端口
server.listen(3000, () => {
  console.log('服务端开启');
});

当我使用本地连接服务端的时候,他的请求头是这样的

{"host":"127.0.0.1:3000","connection":"keep-alive","sec-ch-ua":"\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Microsoft Edge\";v=\"122\"","sec-ch-ua-mobile":"?0","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0","sec-ch-ua-platform":"\"Windows\"","accept":"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8","sec-fetch-site":"same-origin","sec-fetch-mode":"no-cors","sec-fetch-dest":"image","referer":"http://127.0.0.1:3000/","accept-encoding":"gzip, deflate, br","accept-language":"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"}

这是使用反向代理之后的请求头

{"host":"fanmail.x3322.net","x-real-ip":"112.234.137.8","x-forwarded-for":"112.234.137.8","remote-host":"112.234.137.8","connection":"close","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0","accept":"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8","referer":"http://fanmail.x3322.net/","accept-encoding":"gzip, deflate","accept-language":"zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6"}

从中对比我们可以看出在下面的请求头中多出了三个参数,x-forwarded-for remote-host x-real-ip,这三个看请求头中的数据都是一样的112.234.137.8这个IP,但是,这三个表达的意思是不一样的,通过上面的解释也可以看出来,因为我这里没有多的虚拟主机来做演示了,所以这三个IP都是一样的,如果我在另外一台虚拟主机上反向代理这台虚拟主机,那么请求头返回的地址就不一样了,x-forwarded-for返回的是用户的真实IP,remote-host x-real-ip返回的是另外一台虚拟主机的IP。所以我们在写服务端代码的时候要用x-forwarded-for的请求参数中的IP地址。为了使我们的代码没有问题,我们还要做一些判定,如果请求头中没有x-forwarded-for参数,就没法获取x-forwarded-for参数,就只能获取他请求时带的IP了。

const http = require('http');
const server = http.createServer((req, res) => {
  // 检查请求头中是否存在 X-Forwarded-For 字段
  const xForwardedFor = req.headers['x-forwarded-for'];
  // 如果存在,打印出来,不存在打印请求IP
  if (xForwardedFor) {
    console.log('X-Forwarded-For:', xForwardedFor);
  } else {
    console.log(req.connection.remoteAddress);
  }
  // 发送响应
  res.end('DONE!');
});
server.listen(3000, () => {
  console.log('服务器开启');
});


这只是示例代码,具体的代码还是要根据使用场景灵活变通的


扫描二维码,在手机上阅读