一. 前言
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 项目
第一步:
第二步:
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
通过浏览器访问验证:
可是却获取到了 127.0.0.1, 这是因为 们的请求到了 Nginx, 然后 Nginx 再将我们的请求转发到 ASP.NET Core 应用程序, 实际上与 ASP.NET Core 应用程序 建立连接的是 Nginx , 所以获取到了服务器本地 IP (Nginx 和程序部署在一台机子上). 请求流程如下图:
三. 解决问题
修改程序代码以便显示更详细的信息:
- 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;
- }
- }
再次访问:
可以看到 X-Real-IP 和 X-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;
- }
- }
我们再次访问:
可以看到我们的真实 ip 被放到 X-Forwarded-For 请求头的第一个 IP,X-Real-IP 获取到的是上一层代理的 ip.
X-Forwarded-For 来自百度百科的解释: X-Forwarded-For 简称 XFF 头, 它代表客户端, 也就是 https://baike.baidu.com/item/HTTP 的请求端真实的 IP, 只有在通过了 HTTP 代理或者负载均衡服务器时才会添加该项. 它不是 RFC 中定义的标准请求头信息, 在 squid 缓存代理服务器开发文档中可以找到该项的详细介绍. 标准格式如下: X-Forwarded-For: client1, proxy1, proxy2. 请求流程如下图:
五. 如何在代码里最小改动
经过上面的讲解, 显而易见我们在代码里无法直接通过 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 中配置
应放在最靠前的位置, 以免有中间件获取到了未重置的 IP 地址.
保持前面的模拟 cdn 第二中情况架构, 再次进行测试:
可以看到通过 RemoteIpAddress 获取到了真实 ip. 这种解决方案算是比较好的了.
这里提一下 Nginx RealIP Module 是 Nginx 获取真实 ip 的一个模块, 有兴趣的同学可以自己去研究一下.
六. 使用组件 Unicorn.AspNetCore
Unicorn.AspNetCore 里面我有封装处理 ip 的中间件.
通过 nuget 安装:
Install-Package Unicorn.AspNetCore
然后在 Program 中添加:
开源地址:
来源: https://www.cnblogs.com/stulzq/p/9946262.html