diff --git a/.env.example b/.env.example index 03e822c..a5b55ca 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,13 @@ ASPNETCORE_ENVIRONMENT=Production ASPNETCORE_URLS=http://0.0.0.0:8080 +# 反向代理必须在转发请求时设置 X-Forwarded-For 和 X-Forwarded-Proto。 +# 仅填写实际直接连接 API 的代理 IP;多个代理依次使用 __0、__1……。 +# 使用 Docker 时通常是宿主机/代理容器在 Docker 网络中的 IP,而非访客 IP。 +# 默认仅信任 127.0.0.1 和 ::1。 +# ReverseProxy__TrustedProxies__0=127.0.0.1 +# ReverseProxy__TrustedProxies__1=::1 + Database__Provider=MySql Database__ApplyMigrationsOnStartup=false Database__CommandTimeoutSeconds=30 diff --git a/src/Jiaowu.Api/Program.cs b/src/Jiaowu.Api/Program.cs index a7fed39..f7f7f27 100644 --- a/src/Jiaowu.Api/Program.cs +++ b/src/Jiaowu.Api/Program.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Net; using System.Text.Json.Serialization; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Domain.System; @@ -20,6 +21,7 @@ using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.RateLimiting; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; @@ -111,6 +113,21 @@ var rabbitMqOptions = builder.Configuration var ssoOptions = builder.Configuration .GetSection(SsoOptions.SectionName) .Get() ?? new SsoOptions(); +var trustedProxyAddresses = builder.Configuration + .GetSection("ReverseProxy:TrustedProxies") + .Get() ?? []; +var trustedProxies = trustedProxyAddresses + .Select(value => + { + if (!IPAddress.TryParse(value, out var address)) + { + throw new InvalidOperationException( + $"ReverseProxy:TrustedProxies contains an invalid IP address: '{value}'."); + } + + return address; + }) + .ToArray(); if (ssoOptions.Enabled && (string.IsNullOrWhiteSpace(ssoOptions.ClientId) || @@ -554,6 +571,18 @@ if (ssoOptions.Enabled) }); } builder.Services.AddAuthorization(); +builder.Services.Configure(options => +{ + options.ForwardedHeaders = + ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.ForwardLimit = 1; + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); + foreach (var proxy in trustedProxies) + { + options.KnownProxies.Add(proxy); + } +}); builder.Services.AddRateLimiter(options => { options.RejectionStatusCode = StatusCodes.Status429TooManyRequests; @@ -668,6 +697,7 @@ builder.Services.AddSwaggerGen(options => var app = builder.Build(); +app.UseForwardedHeaders(); app.UseExceptionHandler(); app.UseResponseCompression(); app.Use(async (context, next) => diff --git a/src/Jiaowu.Api/appsettings.json b/src/Jiaowu.Api/appsettings.json index 17c8fc1..c1d0064 100644 --- a/src/Jiaowu.Api/appsettings.json +++ b/src/Jiaowu.Api/appsettings.json @@ -105,6 +105,12 @@ "FrontendBaseUrl": "", "CallbackUrl": "" }, + "ReverseProxy": { + "TrustedProxies": [ + "127.0.0.1", + "::1" + ] + }, "Cors": { "Origins": [ "capacitor://localhost",