Nginx 代理配置与 C# 中真实 IP 获取指南
目录
1. Nginx proxy_set_header 指令解析
1.1 基本语法
proxy_set_header X-Real-IP $remote_addr;
1.2 参数说明
| 参数 | 说明 |
|---|---|
proxy_set_header |
Nginx 指令,用于设置转发给后端服务器的 HTTP 请求头 |
X-Real-IP |
自定义 HTTP 请求头名称(非标准头部,但广泛使用) |
$remote_addr |
Nginx 内置变量,代表直接连接到 Nginx 的客户端 IP 地址 |
1.3 工作原理
当 Nginx 作为反向代理时:
客户端 → Nginx → 后端服务器
没有设置的情况:
- 后端服务器看到的 remote_addr 是 Nginx 服务器的 IP
- 后端无法知道原始客户端的真实 IP
设置后的效果:
- Nginx 在转发请求时添加 X-Real-IP: 客户端真实IP 头部
- 后端服务器可以从这个头部读取原始客户端的 IP
1.4 相关头部对比
| 头部 | 格式 | 特点 | 用途 |
|---|---|---|---|
| X-Real-IP | X-Real-IP: 123.123.123.123 |
单个 IP,通常是最后一个代理设置的 | 获取客户端真实 IP |
| X-Forwarded-For | X-Forwarded-For: client, proxy1, proxy2 |
IP 链,记录所有经过的代理 | 追踪请求经过的完整代理链 |
| X-Forwarded-Proto | X-Forwarded-Proto: https |
原始请求协议 | 识别原始请求是 HTTP 还是 HTTPS |
| X-Forwarded-Host | X-Forwarded-Host: example.com |
原始请求的主机头 | 保留原始域名 |
2. C# 中获取真实 IP 地址
2.1 ASP.NET Core (3.x/5/6/7/8)
2.1.1 基本获取方式
// 在 Controller 中直接获取
public IActionResult Get()
{
var realIp = Request.Headers["X-Real-IP"].FirstOrDefault();
return Ok($"Real IP: {realIp}");
}
2.1.2 完整的获取逻辑(推荐)
public string GetClientIpAddress(HttpRequest request)
{
// 1. 首先尝试从 X-Real-IP 获取
if (request.Headers.TryGetValue("X-Real-IP", out var realIp))
{
return realIp.ToString();
}
// 2. 尝试从 X-Forwarded-For 获取(如果有多个代理)
if (request.Headers.TryGetValue("X-Forwarded-For", out var forwardedFor))
{
// X-Forwarded-For 格式:client, proxy1, proxy2
var ipList = forwardedFor.ToString().Split(',');
return ipList[0].Trim(); // 第一个是客户端真实IP
}
// 3. 使用远程IP地址(不经过代理时的直接连接)
return request.HttpContext.Connection.RemoteIpAddress?.ToString();
}
2.1.3 中间件方式
// 自定义中间件
public class RealIpMiddleware
{
private readonly RequestDelegate _next;
public RealIpMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// 获取真实IP
var realIp = context.Request.Headers["X-Real-IP"].FirstOrDefault();
if (!string.IsNullOrEmpty(realIp))
{
// 将IP存储在HttpContext中供后续使用
context.Items["ClientRealIP"] = realIp;
// 设置到Connection.RemoteIpAddress
if (IPAddress.TryParse(realIp, out var ipAddress))
{
context.Connection.RemoteIpAddress = ipAddress;
}
}
await _next(context);
}
}
// 注册中间件
app.UseMiddleware<RealIpMiddleware>();
2.2 ASP.NET MVC 5 / Web API 2(传统 .NET Framework)
public string GetClientIpAddress()
{
// 优先级:X-Real-IP > X-Forwarded-For > 直接连接IP
string ip = Request.Headers["X-Real-IP"];
if (string.IsNullOrEmpty(ip))
{
ip = Request.Headers["X-Forwarded-For"];
if (!string.IsNullOrEmpty(ip))
{
// 可能有多个IP,取第一个
ip = ip.Split(',')[0].Trim();
}
}
if (string.IsNullOrEmpty(ip))
{
ip = Request.UserHostAddress;
}
return ip;
}
2.3 完整的工具类
public static class ClientIpHelper
{
/// <summary>
/// 获取客户端真实IP地址
/// </summary>
public static string GetClientRealIp(HttpRequest request)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
// 优先级顺序
string[] headerKeys =
{
"X-Real-IP", // Nginx
"X-Forwarded-For", // 标准代理头
"CF-Connecting-IP", // Cloudflare
"True-Client-IP", // Akamai和Cloudflare
"WL-Proxy-Client-IP", // WebLogic
"Proxy-Client-IP", // 其他代理
"HTTP_CLIENT_IP", // 传统方式
"HTTP_X_FORWARDED_FOR" // 传统方式
};
// 检查各种可能的头部
foreach (var key in headerKeys)
{
if (request.Headers.TryGetValue(key, out var value))
{
string ip = value.ToString();
// 处理 X-Forwarded-For 的多个IP情况
if (key == "X-Forwarded-For" && ip.Contains(','))
{
ip = ip.Split(',')[0].Trim();
}
// 验证IP格式
if (IsValidIpAddress(ip))
return ip;
}
}
// 最后使用连接IP
return request.HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
}
/// <summary>
/// 验证IP地址格式
/// </summary>
private static bool IsValidIpAddress(string ip)
{
if (string.IsNullOrWhiteSpace(ip))
return false;
return IPAddress.TryParse(ip, out _);
}
}
2.4 Action Filter 示例
public class LogRealIpAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var request = context.HttpContext.Request;
var realIp = ClientIpHelper.GetClientRealIp(request);
// 记录日志
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<LogRealIpAttribute>>();
logger.LogInformation($"Client Real IP: {realIp}");
// 存储到HttpContext.Items
context.HttpContext.Items["ClientRealIP"] = realIp;
base.OnActionExecuting(context);
}
}
// 使用方式
[ApiController]
[Route("api/[controller]")]
[LogRealIp]
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
var realIp = HttpContext.Items["ClientRealIP"] as string;
return Ok(new { RealIP = realIp });
}
}
3. 安全注意事项
3.1 头部伪造防护
- 不要信任未经代理的头部:客户端可以直接伪造
X-Real-IP头部 - 配置可信代理:只接受来自可信代理服务器的这些头部
- 内网代理验证:确保只从内网代理接收这些头部
3.2 Nginx 安全配置
# 只允许特定IP段设置真实IP
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
# 使用X-Forwarded-For头部的最后一个IP作为真实IP
real_ip_header X-Forwarded-For;
# 递归查找,跳过内网IP
real_ip_recursive on;
3.3 ASP.NET Core 安全配置
// 配置Forwarded Headers中间件
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// 信任哪些代理(内网代理)
options.KnownProxies.Add(IPAddress.Parse("10.0.0.0"));
options.KnownProxies.Add(IPAddress.Parse("172.16.0.0"));
options.KnownProxies.Add(IPAddress.Parse("192.168.0.0"));
});
// 使用Forwarded Headers中间件
app.UseForwardedHeaders();
4. 完整配置示例
4.1 Nginx 完整配置
server {
listen 80;
server_name example.com;
location / {
# 后端服务器地址
proxy_pass http://localhost:5000;
# 传递真实IP和相关信息
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header Host $http_host;
# 连接超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 启用WebSocket支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 静态文件缓存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
4.2 ASP.NET Core Program.cs 完整配置
var builder = WebApplication.CreateBuilder(args);
// 添加服务
builder.Services.AddControllers();
// 配置Forwarded Headers
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
// 信任内网代理
options.KnownProxies.Add(IPAddress.Parse("10.0.0.0"));
options.KnownProxies.Add(IPAddress.Parse("172.16.0.0"));
options.KnownProxies.Add(IPAddress.Parse("192.168.0.0"));
});
var app = builder.Build();
// 中间件配置
app.UseForwardedHeaders();
app.UseMiddleware<RealIpMiddleware>();
// 路由配置
app.MapControllers();
// IP查询端点
app.MapGet("/ip", (HttpContext context) =>
{
var realIp = context.Request.Headers["X-Real-IP"].FirstOrDefault();
var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
var remoteIp = context.Connection.RemoteIpAddress?.ToString();
return new
{
RealIP = realIp,
ForwardedFor = forwardedFor,
RemoteIP = remoteIp,
Headers = context.Request.Headers
.Where(h => h.Key.Contains("IP") || h.Key.Contains("Forward"))
.ToDictionary(h => h.Key, h => h.Value.ToString())
};
});
app.Run();
5. 使用场景
5.1 日志记录
// 记录访问日志时包含真实IP
public class AccessLogMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<AccessLogMiddleware> _logger;
public AccessLogMiddleware(RequestDelegate next, ILogger<AccessLogMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var realIp = ClientIpHelper.GetClientRealIp(context.Request);
var method = context.Request.Method;
var path = context.Request.Path;
var userAgent = context.Request.Headers["User-Agent"].ToString();
_logger.LogInformation(
"[{RealIP}] {Method} {Path} - {UserAgent}",
realIp, method, path, userAgent);
await _next(context);
}
}
5.2 访问控制
// 基于IP的访问限制
public class IpWhitelistMiddleware
{
private readonly RequestDelegate _next;
private readonly List<IPAddress> _whitelist;
public IpWhitelistMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
// 从配置读取白名单IP
var whitelistIps = configuration.GetSection("IpWhitelist").Get<string[]>();
_whitelist = whitelistIps.Select(IPAddress.Parse).ToList();
}
public async Task InvokeAsync(HttpContext context)
{
var realIp = ClientIpHelper.GetClientRealIp(context.Request);
if (IPAddress.TryParse(realIp, out var clientIp))
{
if (!_whitelist.Contains(clientIp))
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
await context.Response.WriteAsync("Access denied");
return;
}
}
await _next(context);
}
}
5.3 限流和防刷
// 基于IP的请求限流
public class RateLimitMiddleware
{
private readonly RequestDelegate _next;
private static readonly ConcurrentDictionary<string, List<DateTime>> _requestLogs = new();
public RateLimitMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var realIp = ClientIpHelper.GetClientRealIp(context.Request);
// 清理过期记录
CleanOldRecords(realIp);
// 检查请求频率(例如:每分钟最多10次)
if (_requestLogs.TryGetValue(realIp, out var requests))
{
if (requests.Count >= 10) // 限制10次/分钟
{
context.Response.StatusCode = StatusCodes.Status429TooManyRequests;
await context.Response.WriteAsync("Rate limit exceeded");
return;
}
}
// 记录本次请求
_requestLogs.AddOrUpdate(realIp,
new List<DateTime> { DateTime.UtcNow },
(key, existing) =>
{
existing.Add(DateTime.UtcNow);
return existing;
});
await _next(context);
}
private void CleanOldRecords(string ip)
{
if (_requestLogs.TryGetValue(ip, out var requests))
{
var cutoff = DateTime.UtcNow.AddMinutes(-1);
requests.RemoveAll(r => r < cutoff);
}
}
}
总结
本文档详细介绍了:
1. Nginx 中 proxy_set_header X-Real-IP $remote_addr 的作用和配置方法
2. C# 中获取真实 IP 地址的多种实现方式
3. 安全注意事项和最佳实践
4. 完整的配置示例和使用场景
通过正确配置 Nginx 和在后端应用中正确处理代理头部,可以确保在多级代理架构中准确获取客户端真实 IP 地址,为访问控制、日志记录、安全审计等功能提供基础支持。
文档最后更新:2024年
适用环境:Nginx 1.18+, .NET Core 3.1+, .NET Framework 4.5+