惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
S
SegmentFault 最新的问题
V
Visual Studio Blog
J
Java Code Geeks
宝玉的分享
宝玉的分享
美团技术团队
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
有赞技术团队
有赞技术团队
量子位
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
博客园 - 叶小钗
月光博客
月光博客
P
Proofpoint News Feed
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog

博客园 - 华安

自适应网格系统:CSS Grid中repeat()、auto-fill与auto-fit的深度解析 CSS3中响应式布局两大神器display:flex和display:grid springBoot中的 pom.xml文件 bulid学习 CSS中元素的display显示方式有多种,隐藏、块级、内联、内联-块级 SQl Server 中的 go 是什么作用 CSS中 display:flex的align-items: stretch; 移动端浏览器(尤其是 iOS Safari)的橡皮筋回弹效果(Overscroll / Bounce Effect) 手机端浏览器上ES6中的Fetch回调执行 window.open没效果 用Flex实现兼容性好的全屏布局 在 VS Code 中使用 C# Dev Kit 和 Unity Tools 调试 Unity 2022 Unity 可编程物件(ScriptableObject) 微信小程序中的 联系客服 最基本的使用方法 wx.requestSubscribeMessage(Object object) 和 wx.requestSubscribeDeviceMessage(Object object) 这两个有什么区别 微信小程序中 wx.hideLoading() 后调用 wx.showToast()的问题 Windows 中启动 Nginx的常用命令 CSS进阶技巧:字体渐变、描边、倒影与渐变色描边全解析 netCore 中各DLL引用了 SkiaSharp.dll的问题 unity中预制体解包 在 Unity 中,Time.timeScale实现游戏暂停加速等 微信小程序中关联微信支付 unity中的 Navigation AI使用 C#中TaskCompletionSource(简称 TCS)学习 Unity 编辑器 中,快捷键 Ctrl + Shift + F 的功能 unity中按下 F键,让物体聚焦 微信中进入定页面的,判断时通过扫二维码进入的,还是点小程序名称进入的 MYSQL中从JSON字符串中提取指定的值 Unity2022中创建动画 Animation(旧方法) Unity 中区别,public 和 [SerializeField] Unity中 onCollisionEnter2D与OnTriggerEnter2D 区别 asp.netCore中给动态请求路径加客户端缓存
netCore中获取客户端真实的IP引起的思考
华安 · 2026-07-22 · via 博客园 - 华安

一、路由器、网关和客户端真实IP获取的关系

1. 典型网络路径

用户 → 家庭/公司路由器 → 外网 →(CDN/负载均衡/代理等)→ IIS服务器

  • 客户端真实 IP指的是发起 HTTP 请求的用户设备(手机、PC等)的公网IP地址。
  • 在互联网架构下,用户通常先通过自己的路由器(尤其是 NAT 路由,如家庭/企业宽带),再访问应用服务器。

2. NAT 网关/路由器的工作方式

  • 家庭/公司用户的路由器做了 NAT(网络地址转换),把多台内网机器映射到一个公网出口IP。
  • 应用服务器只能看到路由器出口的公网IP,无法获知局域网里的真实内网IP。

3. 代理/CDN/WAF等“网关”作用

  • 如果前面有代理服务器/负载均衡(比如 CDN、IIS ARR、F5、阿里云WAF),应用服务器只能直接看到上一跳(代理/网关)的 IP。
  • 是否能拿到用户真实IP,依赖于上游设备是否把用户IP(即NAT出口IP)通过特定HTTP头(如 X-Forwarded-For)传给下游
  • 没有代理/负载情况下,就是浏览器发起请求时的实际公网IP(即NAT出口IP)。

二、你能获取到的“客户端IP”到底是谁的?

  • 你的IIS服务器直接对互联网开放:只能拿到用户的路由器的公网出口IP。
  • 用户在公司/家庭,IIS看到的是公司的公网IP/家庭宽带IP,无法进一步区分局域网中的某台机器。
  • 你的IIS服务器位于内网、只接受内网请求:只能拿到发起请求的那台内网机器的IP。
  • 路由器/代理/网关前有更多跳:你拿到的往往是上一层设备的IP,只有经过正确的转发/头部才可能获得最前面的IP。

三、什么情况下“获取不到客户端真实IP”?

  • 路由器/NAT作用:只暴露公网出口IP,你无法直接获取家庭/公司内部设备的局域网IP。
  • 若前面有代理、网关、CDN,并且没有正确设置转发真实IP请求头,你拿到的其实是代理的IP。
  • IIS/ASP.NET Core,如果前面有代理但你没配置 UseForwardedHeaders 或 KnownProxies,也只会拿到代理出口IP。

四、如何最大限度获取“最真实的客户端IP”?

  1. 没有代理/负载,直接外网访问你服务:

    • 拿到的是客户端所用路由器的公网出口IP。
  2. 有代理/负载(如CDN/网关/反向代理/IIS ARR等):

    • 需要上游代理配置添加 X-Forwarded-For(链条中的每一跳加一个IP)。
    • 你的应用启用 UseForwardedHeaders,正确配置 KnownProxies/KnownNetworks。
    • 这样,拿到的是“最前端发起请求的公网IP”,但如果原始请求已是NAT后的IP,无法再往前追溯到局域网IP。
  3. 你的扩展方法最终拿到的是“HTTP链最前端设备的公网IP”——即NAT后的地址,无法拿到局域网真实IP。

五、示意图

1784708568739

你只能看到 123.45.67.89,而没有办法得知 192.168.1.20。

六、回答总结

  • 路由器的网关设置,决定了你拿到的“客户端IP”最多是其公网出口IP,不是局域网IP。
  • 若还有代理/CDN/网关在IIS前面,且未正确转发客户端IP头部,则你拿到的是代理的出口IP。
  • IIS只能从请求头(如 X-Forwarded-For)或 RemoteIpAddress 拿到最前端的IP——这是互联网现实,受限于网络架构和设备能力。
  • 你无法直接获取家庭/公司局域网里的真实IP,只能拿到最外层注册地址。

如你需要进一步区分用户(家庭内多台机器),可以考虑:

  • 用户登录后采集信息(如 User-Agent、Cookie等)。
  • 若是在同一公网出口,IP会一样,只能根据其它手段区分。**
public class ClientIpResolverHelper
{
    #region 内部私有方法

    private static string NormalizeIp(string ip)
    {
        if (string.IsNullOrEmpty(ip)) return null;
        if (!IPAddress.TryParse(ip, out var address)) return ip;

        if (address.IsIPv4MappedToIPv6)
            return address.MapToIPv4().ToString();

        return address.ToString();
    }

    private static bool IsPrivateIp(string ip)
    {
        if (!IPAddress.TryParse(ip, out var address)) return false;
        return IsPrivateIp(address);
    }

    private static bool IsPrivateIp(IPAddress address)
    {
        if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
        {
            return address.IsIPv6LinkLocal ||
                   address.IsIPv6SiteLocal ||
                   address.ToString().StartsWith("fc") ||  // Unique local
                   address.ToString().StartsWith("fd");
        }

        var bytes = address.GetAddressBytes();
        return (bytes[0] == 10) ||                                    // 10.0.0.0/8
               (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) || // 172.16.0.0/12
               (bytes[0] == 192 && bytes[1] == 168) ||                  // 192.168.0.0/16
               (bytes[0] == 127);                                       // 127.0.0.0/8
    }

    #endregion
    /// <summary>
    /// 获取客户端IP
    /// </summary>
    /// <param name="htc"></param>
    /// <returns></returns>
    public static string GetClientIp(HttpContext htc)
    {
        HttpContext hct = GDCHttpContext.Current;
        var remoteIp = hct.Connection.RemoteIpAddress?.ToString();

        // 如果不是私有 IP,直接返回(直连客户端)
        if (!string.IsNullOrEmpty(remoteIp) && !IsPrivateIp(remoteIp))
        {
            return NormalizeIp(remoteIp);
        }

        // 尝试从转发头获取
        var forwarded = hct.Request.Headers["X-Forwarded-For"].FirstOrDefault()
                        ?? hct.Request.Headers["X-Real-IP"].FirstOrDefault();

        if (string.IsNullOrEmpty(forwarded))
        {
            return NormalizeIp(remoteIp) ?? "unknown";
        }

        // 安全处理:从右向左遍历,找到最后一个可信的公网 IP
        var ips = forwarded.Split(',')
                           .Select(s => s.Trim())
                           .Where(s => !string.IsNullOrEmpty(s))
                           .ToList();

        // 遍历顺序:从离服务器最近的代理向客户端方向检查
        for (int i = ips.Count - 1; i >= 0; i--)
        {
            var ip = ips[i];
            if (IPAddress.TryParse(ip, out var address))
            {
                // 如果是公网 IP,返回它
                if (!IsPrivateIp(address))
                {
                    return NormalizeIp(address.ToString());
                }
            }
        }
        // 如果全是内网 IP,返回第一个(最原始的)
        return NormalizeIp(ips.FirstOrDefault()) ?? "unknown";
    }
}

 还有一步很重要:

//HttpContext.Connection.RemoteIpAddress 会自动变为X-Forwarded-For的第一个IP。
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});