• ASP.NET Core 搭配 Nginx 的真实IP问题


    一.前言

    img

    Nginx(Engine X)是一个高性能HTTP和反向代理服务,是由俄罗斯人伊戈尔·赛索耶夫为访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。 如果你是一名 ASP.NET Core 开发人员,并且你的 ASP.NET Core 应用部署在Linux上,相信你应该或多或少与 Nginx 有过接触,在我们将 ASP.NET Core 部署在 Linux 上时,它是被用做反向代理的最好选择之一。今天和大家聊一聊当我们使用了 Nginx 反向代理后,我们程序中获取真实IP(客户端真实ip,本文简称“真实IP”)的问题。

    二.发现问题

    1.安装 Nginx

    这里我就选用我安装在 CentOS 7.2 上的 Nginx,在 CentOS 安装 Nginx 的同学可以参考我以前写的文章:CentOS 7 源码编译安装 Nginx

    2.新建 ASP.NET Core 项目

    第一步:

    1541941721350

    第二步:

    1541941745624

    3.编写代码

    编辑 ValuesController

            private readonly HttpContext _context;
            public ValuesController(IHttpContextAccessor accessor)
            {
                _context = accessor.HttpContext;
            }
            // GET api/values
            [HttpGet]
            public ActionResult<IEnumerable<string>> Get()
            {
                return Ok($"获取到的真实IP:{_context.Connection.RemoteIpAddress}");
            }
    

    编辑 Startup

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            }
    

    4.测试

    (1)将程序部署到服务器

    本文略此步

    (2)配置 Nginx 反向代理

    新建配置文件 realiptest.conf

    server {
        listen 5002;
        access_log  off;
        location / {
           proxy_pass http://localhost:5000; 
        }
    }
    

    (3)测试访问

    服务器地址:192.168.157.132

    我本机地址:192.168.157.1

    那么我本机通过访问 http://192.168.157.132:5002/api/values api获取到的ip地址应该是我本机的,即 192.168.157.1

    通过浏览器访问验证:

    1541946415816

    可是却获取到了 127.0.0.1,这是因为 们的请求到了 Nginx,然后 Nginx 再将我们的请求转发到 ASP.NET Core 应用程序,实际上与 ASP.NET Core 应用程序 建立连接的是 Nginx ,所以获取到了服务器本地 IP (Nginx和程序部署在一台机子上)。请求流程如下图:

    1541984724645

    三.解决问题

    修改程序代码以便显示更详细的信息:

    ValuesController

            // GET api/values
            [HttpGet]
            public ActionResult<IEnumerable<string>> Get()
            {
                StringBuilder sb=new StringBuilder();
                sb.AppendLine($"RemoteIpAddress:{_context.Connection.RemoteIpAddress}");
    
                if (Request.Headers.ContainsKey("X-Real-IP"))
                {
                    sb.AppendLine($"X-Real-IP:{Request.Headers["X-Real-IP"].ToString()}");
                }
    
                if (Request.Headers.ContainsKey("X-Forwarded-For"))
                {
                    sb.AppendLine($"X-Forwarded-For:{Request.Headers["X-Forwarded-For"].ToString()}");
                }
                return Ok(sb.ToString());
            }
    

    修改反向代理配置:

    server {
        listen 5002;
        access_log  off;
        location / {
           proxy_set_header   X-Real-IP        $remote_addr;
           proxy_set_header   Host             $host;
           proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
           proxy_pass                          http://localhost:5000;
        }
    }
    
    

    再次访问:

    1541948087185

    可以看到 X-Real-IPX-Forwarded-For请求头获取到了真实IP,我们通过修改 Nginx 配置,让程序接收到的请求信息携带真实IP。Nginx 通过在 X-Real-IP 、X-Forwarded-For 请求头设置了与它连接的远程ip

    以上解决办法对于没有使用CDN是适用的。

    四.使用CDN如何解决

    我们的请求经过一个或者多个cdn结点以后,我们的程序如何获取真实IP呢,这就要看cdn服务商提供的解决办法了,一般有两种:

    1.cdn服务商支持设置真实ip到某个指定的请求头,这样我们通过这个请求头就能获取了 。

    2.一般经过cdn都会把真实ip经过的结点ip信息添加到头 X-Forwarded-For,我们取这个头里的第一个ip就是真实ip。

    添加 nginx 配置,让他再次代理 5002 端口(前面添加的代理ASP.NET Core 程序),模拟cdn第二种方案:

    server {
        listen 5003;
        access_log  off;
        location / {
           proxy_set_header   X-Real-IP        $remote_addr;
           proxy_set_header   Host             $host;
           proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
           proxy_pass                          http://192.168.157.132:5002;
        }
    }
    
    

    我们再次访问:

    1541949268726

    可以看到我们的真实ip被放到 X-Forwarded-For 请求头的第一个IP,X-Real-IP 获取到的是上一层代理的ip。

    X-Forwarded-For 来自百度百科的解释:X-Forwarded-For 简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项。它不是RFC中定义的标准请求头信息,在squid缓存代理服务器开发文档中可以找到该项的详细介绍。标准格式如下:X-Forwarded-For: client1, proxy1, proxy2。请求流程如下图:

    1541985228889

    五.如何在代码里最小改动

    经过上面的讲解,显而易见我们在代码里无法直接通过 RemoteIpAddress 获取真实ip,那么如果我们在编写代码时,很多地方直接采用 RemoteIpAddress获取真实ip怎么办,难道需要修改每一处吗,这里分享一个简单的解决办法,就是利用 ASP.NET Core 中间件给 RemoteIpAddress 重新赋值。

    编写 RealIpMiddleware 中间件:

    public class RealIpMiddleware
    {
        private readonly RequestDelegate _next;
    
        public RealIpMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public Task Invoke(HttpContext context)
        {
            var headers = context.Request.Headers;
            if (headers.ContainsKey("X-Forwarded-For"))
            {
                context.Connection.RemoteIpAddress=IPAddress.Parse(headers["X-Forwarded-For"].ToString().Split(',', StringSplitOptions.RemoveEmptyEntries)[0]);
            }
            return _next(context);
        }
    }
    

    如果是前面提到的cdn的第一种情况,只需判断cdn服务商提供的特殊请求头就行了。

    在Startup中配置

    1541950121051

    应放在最靠前的位置,以免有中间件获取到了未重置的IP地址。

    保持前面的模拟cdn第二中情况架构,再次进行测试:

    1541950299862

    可以看到通过 RemoteIpAddress 获取到了真实ip。这种解决方案算是比较好的了。

    这里提一下 Nginx RealIP Module 是 Nginx 获取真实ip的一个模块,有兴趣的同学可以自己去研究一下。

    六.使用组件 Unicorn.AspNetCore

    Unicorn.AspNetCore 里面我有封装处理ip的中间件。

    通过nuget安装:

    Install-Package Unicorn.AspNetCore
    

    然后在 Program 中添加:

    1542003717012

    开源地址:https://github.com/UCPlan/Unicorn/tree/master/src/Infrastructure/Unicorn.AspNetCore/Middleware/RealIp

    官方解决方案

    官方对于这个问题,有封装中间件,可以直接使用,详情请点击 点我

  • 相关阅读:
    sql 中 in 与 exist 的区别
    with as (cte common table expression) 公共表表达式
    配置连接数据库的方式
    Dispose 与 close 方法 的区别
    抽象类
    default
    什么叫无符号整型
    hdu 5187 zhx's contest [ 找规律 + 快速幂 + 快速乘法 || Java ]
    poj 2480 Longge's problem [ 欧拉函数 ]
    lightoj 1293
  • 原文地址:https://www.cnblogs.com/stulzq/p/9946262.html
Copyright © 2020-2023  润新知