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 头部伪造防护

  1. 不要信任未经代理的头部:客户端可以直接伪造 X-Real-IP 头部
  2. 配置可信代理:只接受来自可信代理服务器的这些头部
  3. 内网代理验证:确保只从内网代理接收这些头部

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+