You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node/MongoDB/Azure+Cloudflare环境下req.ip返回IP格式一致性问询

问题解答:Cloudflare环境下Express获取用户真实IP的一致性问题

咱们一步步来拆解你的三个问题,结合Cloudflare、Express和IPv4/IPv6的实际场景分析:

1. 客户端ipinfo.io与服务端req.ip返回值为何不同?

核心原因是服务端可能没正确读取Cloudflare传递的真实用户IP:

  • 客户端请求ipinfo.io时,直接从用户设备的公网IP发起,拿到的是最原始的用户出口IP;
  • 而你的服务端通过Cloudflare代理后,Cloudflare会用CF-Connecting-IP头专门传递用户的真实IP,但Express默认的trust proxy逻辑是解析X-Forwarded-For头——这个头会包含用户IP + Cloudflare边缘节点的IP,如果你没指定优先用CF-Connecting-IP,就可能拿到Cloudflare的节点IP,而非用户真实IP,自然和客户端的结果不一致。

2. req.ip为何有时返回IPv4,有时返回IPv6?

这完全取决于用户的网络环境:

  • 有些用户的ISP只提供IPv4网络,Cloudflare会用IPv4和用户建立连接,传递的真实IP就是IPv4格式;
  • 支持IPv6(或双栈)网络的用户,Cloudflare会优先用IPv6连接,此时传递的真实IP就是IPv6格式;
  • 另外,如果你的Azure服务器配置了双栈(同时支持IPv4/IPv6),Cloudflare会根据用户的IP类型选择对应的协议转发请求,但核心还是用户的网络出口协议决定了IP格式。

3. 如何让req.ip返回一致格式(优先IPv4),统一限流记录?

可以通过「修正IP获取逻辑 + 标准化IP格式」两步来解决:

第一步:确保服务端拿到真实用户IP

先添加一个中间件,优先读取Cloudflare的CF-Connecting-IP头,覆盖默认的req.ip:

// 放在app.set('trust proxy', true)之后
app.use((req, res, next) => {
  // 优先用CF-Connecting-IP, fallback到req.ip
  req.ip = req.headers['cf-connecting-ip'] || req.ip;
  next();
});

为了安全性,建议不要直接设trust proxy: true,而是指定Cloudflare的IP范围,防止恶意请求伪造头:

const cloudflareIps = require('cloudflare-ips');
app.set('trust proxy', cloudflareIps.cidrs); // 只信任Cloudflare的代理IP

第二步:标准化IP格式(优先IPv4)

用ip库(npm install ip)把IPv4映射的IPv6地址转换成纯IPv4,纯IPv6则保留(无法强制转换成IPv4):

const ip = require('ip');

app.use((req, res, next) => {
  let userIp = req.headers['cf-connecting-ip'] || req.ip;
  // 把::ffff:xxx.xxx.xxx.xxx这类IPv4映射的IPv6转换成纯IPv4
  if (ip.isV6Format(userIp) && ip.isV4MappedAddress(userIp)) {
    userIp = ip.toV4(userIp);
  }
  req.ip = userIp;
  next();
});

第三步:统一限流的Key

在Express Rate Limit中指定用处理后的req.ip作为限流Key,确保MongoDB中的记录格式统一:

const rateLimit = require('express-rate-limit');
const MongoStore = require('rate-limit-mongo');

const limiter = rateLimit({
  store: new MongoStore({
    uri: process.env.MONGODB_URI,
    collectionName: 'rateLimits',
  }),
  windowMs: 5 * 60 * 1000, // 5分钟窗口
  max: 3, // 最多3次请求
  keyGenerator: (req) => req.ip, // 使用标准化后的IP作为限流标识
});

app.use(limiter);

如果业务需要完全统一(哪怕纯IPv6用户),也可以对所有IP生成哈希值(比如MD5)作为Key,但这样会丢失原始IP的可读性,按需选择即可。

内容的提问来源于stack exchange,提问作者user1063287

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 12:27:52