在基于 Nuxt 开发个人博客项目时,我遇到了一个核心痛点:生产环境通过路由代理转发请求后,后端服务始终无法获取用户真实 IP,要么固定显示服务器出口 IP,要么 IP 随机跳动,直接导致博客阅览量统计失真、当日点赞防刷机制完全失效。


经过逐层排查,问题根源集中在Nuxt 路由代理配置、EdgeOne (EO) CDN 透传规则、Nginx 反向代理、后端 IP 获取逻辑四个环节的链路断裂。本文将完整复盘问题场景、根因分析与最终解决方案,帮助大家规避同类代理环境下的 IP 透传坑点。



一、问题场景复现:两种错误配置引发的 IP 异常

我的新项目采用Nuxt3作为前端框架,通过routeRules配置路由代理转发/api//emoji/请求,后端使用 Java 服务,接入腾讯云 EdgeOne (EO) CDN 加速,前端请求经过「用户→EO→Nginx→Nuxt 代理→后端服务」的全链路,IP 获取异常分两个阶段:


阶段 1:代理指向远程服务,IP 固定为服务器出口 IP

最初的 Nuxt 配置直接将请求代理到远程正式服务:

// nuxt.config.ts 错误配置1
routeRules: {
  '/api/**': { proxy: 'https://www.dreamcenter.top/api/**' },
  '/emoji/**': { proxy: 'https://www.dreamcenter.top/emoji/**' }
}

问题表现:后端通过request.getHeader("X-Real-IP")获取的 IP永远固定为 101.43.71.114(服务器出口 IP),所有用户的请求都被识别为同一个 IP,阅览量统计、防刷逻辑完全失效。

根因:请求直接代理到远程域名,经过 EO CDN 和服务器多层转发后,Nuxt 代理没有透传用户真实 IP,后端只能收到最上层代理的服务器 IP。


阶段 2:代理指向本地服务,IP 随机跳动

为了解决远程代理问题,我修改配置区分开发 / 生产环境,生产环境将请求代理到本地后端服务:

// nuxt.config.ts 优化配置
routeRules: {
  '/api/**': { 
    proxy: process.env.NODE_ENV === 'development' 
      ? 'https://www.dreamcenter.top/api/**'  // 开发环境代理远程
      : 'http://127.0.0.1:9999/api/**'        // 生产环境代理本地后端
  },
  '/emoji/**': {
    proxy: process.env.NODE_ENV === 'development' 
      ? 'https://www.dreamcenter.top/emoji/**'
      : 'http://127.0.0.1:9999/emoji/**'
  }
}

问题表现:IP 不再固定,但每次请求的 IP 都随机变化,后端依然无法识别真实用户。

根因:请求经过 EdgeOne CDN 转发后,IP 被多层代理封装,单纯依赖X-Real-IP只能获取到代理层 IP,而非用户真实 IP。



二、核心原理:多层代理下的 IP 透传规则

EO CDN → Nginx → Nuxt → 后端服务的多层代理架构中,用户真实 IP 会被层层覆盖,必须通过标准请求头透传:

  1. EdgeOne 会将用户真实 IP 放在EO-Real-IP请求头中;
  2. Nginx 需要接收 EO 的真实 IP,并追加到X-Forwarded-ForX-Real-IP
  3. Nuxt 本地代理不篡改请求头,直接转发 Nginx 传递的 IP 头;
  4. 后端不能只依赖单个请求头,必须兼容多层代理的标准头格式。



三、全链路解决方案:从 Nginx 到后端的 IP 透传配置

1. Nginx 配置:开启 EO 真实 IP 透传

Nginx 只需配置标准代理请求头转发规则,即可将 EdgeOne 传递的用户真实 IP 透传给 Nuxt。经实测,无需启用 set_real_ip_fromreal_ip_header 等针对 EO 的专用配置,相关配置项暂时注释,后续可根据运行情况观察是否需要为 EO 单独配置 EO-Real-IP

Nginx 会将包含用户真实 IP 的信息封装到标准请求头中转发给 Nuxt,其中 X-Forwarded-For 是多层代理环境下获取用户真实 IP 最关键的请求头,能够完整记录代理链路中的所有 IP 信息,保障后端可以正确解析。

server {
    listen       443 ssl;
    server_name  dreamcenter.top;
    
    # 核心:开启EdgeOne真实IP透传 (实测配不配置貌似都不影响,先注释,后续待观察验证是否需要给EO单独配置一个EO-Real-IP)
    # set_real_ip_from  101.43.71.114;  # 信任EO出口IP
    # real_ip_header    EO-Real-IP;     # 从EO自定义头获取真实IP
    # real_ip_recursive on;             # 递归获取最上层真实IP

    # SSL配置(原有配置保留)
    ssl_certificate      C:\data\ssl/dreamcenter.top_nginx/dreamcenter.top_bundle.crt;
    ssl_certificate_key  C:\data\ssl/dreamcenter.top_nginx/dreamcenter.top.key;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_protocols TLSv1.2 TLSv1.3; 
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 
    ssl_prefer_server_ciphers  on;

    # 静态资源配置(原有配置保留)
    location = /baidu_verify_codeva-vmodcnnE0h.html {
        alias C:/data/dreamcenter/stable/baidu_verify_codeva-vmodcnnE0h.html;
    }
    location /ads.txt {
        alias C:/data/dreamcenter/stable/ads.txt;
    }
    location /stable/ {
        alias C:/data/dreamcenter/stable/;
    }

    # 转发Nuxt服务,标准传递IP请求头
    location / {
        proxy_pass http://127.0.0.1:33330;  # 转发Nuxt服务
        
        # 核心:透传真实IP请求头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;          # 单层代理IP
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  # 多层代理IP链
    }
}

关键作用:Nginx 会将 EO 传递的真实 IP,替换为标准的 X-Forwarded-ForX-Real-IP 请求头,转发给 Nuxt。(实际最有作用的是 X-Forwarded-For


2. Nuxt 配置:本地代理不篡改请求头

保持上文区分环境的代理配置即可:生产环境直接代理本地后端服务,Nuxt 会自动透传 Nginx 传递的 IP 请求头,无需额外配置。


3. 后端工具类:兼容多层代理的真实 IP 获取

后端放弃单一X-Real-IP获取方式,编写通用工具类,优先读取标准多层代理头X-Forwarded-For,兼容所有代理场景:

package top.dreamcenter.dreamcenter.utils;

import javax.servlet.http.HttpServletRequest;

public class CommonUtil {
    /**
     * 获取用户真实IP,兼容多层代理、CDN、Nginx场景
     */
    public static String getClientIp(HttpServletRequest request) {
        // 1. 优先从X-Forwarded-For获取(标准多层代理头,格式:用户IP,代理1,代理2...)
        String ip = request.getHeader("X-Forwarded-For");

        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            // 取第一个非unknown的IP,即用户真实IP
            return ip.split(",")[0].trim();
        }

        // 2. 其次读取X-Real-IP(Nginx直接传递的IP)
        ip = request.getHeader("X-Real-IP");
        if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
            return ip;
        }

        // 3. 兜底:直接获取连接IP(无代理时生效)
        ip = request.getRemoteAddr();

        // 4. 最终兜底:返回匿名标识
        return (ip == null || ip.isEmpty()) ? "anonymous" : ip;
    }
}



四、最终效果

完成以上三层配置后,项目完美解决了 IP 获取问题:

  1. 阅览量统计精准:每个用户的真实 IP 被正确识别,访问记录不再重复 / 失真;
  2. 点赞防刷生效:基于真实 IP 限制当日点赞次数,杜绝恶意刷赞;
  3. 全链路兼容:开发环境代理远程、生产环境代理本地,均能正常获取 IP。



五、总结:多层代理 IP 透传核心要点

  1. CDN 层:确认服务商的真实 IP 请求头(如 EO 的EO-Real-IP),配置信任 IP(暂且貌似不用额外配置);
  2. 代理层(Nginx):必须开启real_ip规则,透传标准 IP 请求头;
  3. 应用层(Nuxt):本地代理优先转发本地服务,避免远程代理丢失 IP;
  4. 后端层:绝不依赖单一 IP 头,必须兼容X-Forwarded-For多层代理标准。

这套方案适用于所有 Nuxt+CDN+Nginx 的前后端分离项目,彻底解决代理环境下的真实 IP 获取难题。




总结

  1. 多层代理下 IP 获取失败的核心是请求头未透传,而非单一配置问题;
  2. Nginx 的real_ip配置是衔接 CDN 与应用的关键,必须信任 CDN IP;
  3. 后端通用 IP 工具类是最终保障,优先读取X-Forwarded-For标准头;
  4. Nuxt 生产环境建议代理本地服务,最大化保留请求头原始信息。