1. 程式人生 > >ASP.NET Core 搭配 Nginx 的真實IP問題

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

通過瀏覽器訪問驗證:

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