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

推荐订阅源

Help Net Security
Help Net Security
Recent Announcements
Recent Announcements
A
About on SuperTechFans
N
News and Events Feed by Topic
I
Intezer
B
Blog
GbyAI
GbyAI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
阮一峰的网络日志
阮一峰的网络日志
T
Troy Hunt's Blog
TaoSecurity Blog
TaoSecurity Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
The Register - Security
The Register - Security
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
P
Privacy & Cybersecurity Law Blog
S
Secure Thoughts
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
Google Developers Blog
罗磊的独立博客
博客园 - 聂微东
G
GRAHAM CLULEY
AWS News Blog
AWS News Blog
Google Online Security Blog
Google Online Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Hacker News: Front Page
C
Check Point Blog
L
Lohrmann on Cybersecurity
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy
O
OpenAI News
T
Tor Project blog
P
Privacy International News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Attack and Defense Labs
Attack and Defense Labs
L
LINUX DO - 最新话题
Forbes - Security
Forbes - Security
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
S
Securelist
博客园_首页

博客园 - 华安

asp.netCore中给动态请求路径加客户端缓存 Unity 相机:正交 (Orthographic) vs 透视 (Perspective) - 华安 unity中的button中的onclick控制面板中四个参数的意思分别是什么 - 华安 微信小程序开发中触发 onshow的几种特殊情况 SQL Server备份心得 MYSQL 备份数据库 微信与支付支付功能开发 微信小程序中页面配置下拉刷新 unity中 相机没有视锥效果线框了,如何打开 JSONPath表达式 C# 中的操作JSON类 JObject cookie中的 HttpOnly 、Secure、SameSite 解释 Spring-boot 中基于 IP 的限流和自动封禁 Filter 登录 用 HMAC-SHA256 实现 TwoFA(二重验证)的坑 利用Spring Boot的 filter 结合ConcurrentHashMap 实现“同一IP每分钟最多允许300个404请求,超出后禁用30分钟访问” pixi-filters中的BackdropBlurFilter使用注意事项 PixiJS中的 SplitBitmapText.chars中的每个 BitmapText的x值分析 legend隐藏不想显示的图例 spring-boot HttpServletResponse response.sendRedirect是会跳转到 http而不是https springboot获取post请求参数 Javascript如何判断是触摸屏还是PC端 JavaScript获取鼠标点一个元素,获取鼠标点击的元素内的位置 spring-boot中配置Mongodbd的问题小结 Rest Template中添加 PATCH请求。 CSS实现修改CheckBox样式 SpringBoot+Thyemleaf报错:Error resolving template Template might not exist or might not be accessible div display flex 如何出现横向滚动条
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
});