netCore中获取客户端真实的IP引起的思考
一、路由器、网关和客户端真实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”?
-
没有代理/负载,直接外网访问你服务:
- 拿到的是客户端所用路由器的公网出口IP。
-
有代理/负载(如CDN/网关/反向代理/IIS ARR等):
- 需要上游代理配置添加
X-Forwarded-For(链条中的每一跳加一个IP)。 - 你的应用启用 UseForwardedHeaders,正确配置 KnownProxies/KnownNetworks。
- 这样,拿到的是“最前端发起请求的公网IP”,但如果原始请求已是NAT后的IP,无法再往前追溯到局域网IP。
- 需要上游代理配置添加
-
你的扩展方法最终拿到的是“HTTP链最前端设备的公网IP”——即NAT后的地址,无法拿到局域网真实IP。
五、示意图

你只能看到 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 });
浙公网安备 33010602011771号