ASP.NET Core 搭配 Nginx 的真實IP問題
一.前言
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
通過瀏覽器訪問驗證:
可是卻獲取到了 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頭,它代表客戶端,也就是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 中新增: