Nginx 代理中的跨源预检请求

Cross Origin preflight request in Nginx Proxy

从浏览器访问时出现此错误

Access to XMLHttpRequest at 'https://api.example.com/users/authenticate' from origin 'https://example.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource

Startup.cs

using System;
using System.Text;
using BackendRest.Helper;
using BackendRest.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;

namespace BackendRest
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }
        readonly string CorsApi = "_CorsApi";
        public IConfiguration Configuration { get; }
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(CorsApi,
                    builder =>
                    {
                        builder
                        .AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader();
                    });
            });
            services.AddControllers().AddNewtonsoftJson();
            services.AddDbContext<backenddbContext>(x => x.UseMySql(Configuration.GetConnectionString("DefaultConnection")));
            services.AddScoped<IUserHelper, UserHelper>();
            services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
            {
                options.RequireHttpsMetadata = false;
                options.SaveToken = true;
                options.TokenValidationParameters = new TokenValidationParameters()
                {
                    ClockSkew = TimeSpan.Zero,
                    ValidateLifetime = true,
                    ValidateIssuer = true,
                    ValidateAudience = true,
                    ValidAudience = Configuration["Jwt:Audience"],
                    ValidIssuer = Configuration["Jwt:Issuer"],
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
                };
            });
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseCors(CorsApi);
            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

Nginx 配置文件

server {
    #listen        80;
    listen                 *:443 ssl;
    ssl_certificate        /etc/ssl/example.com.pem;
    ssl_certificate_key    /etc/ssl/example.com.key;
    if ($host != "api.example.com") {
  return 404;
 }
    server_name            api.example.com;
    location / {
    proxy_pass      https://localhost:5001;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_http_version 1.1;
    proxy_set_header   Upgrade $http_upgrade;
    proxy_set_header   Connection keep-alive;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header   X-Forwarded-Proto $scheme;
  }
}

在控制器中也启用了 Cors 策略通过将波纹管代码添加到控制器顶部

[EnableCors("CorsApi")]

前端发送请求函数:

export const login = async ({ username, password }) => {
  const result = await axios.post(
    `${BASE_URL}/users/authenticate`,
    JSON.stringify({ username, password }),
    {
      headers: {
        'Content-Type': 'application/json',
      },
    }
  );

  return result;
};

本地主机服务器的启动设置:

"Kestrel": {
    "Endpoints": {
      "HTTPS": {
        "Url": "https://localhost:5001",
        "Certificate": {
          "Path": "/etc/ssl/example.pfx",
          "Password": "james343"
        }
      }
    }
  }

启动 Locahost 服务器的服务:

[Unit]
Description=My first .NET Core application on Ubuntu

[Service]
WorkingDirectory=/home/ubuntu/example
ExecStart=/usr/bin/dotnet /home/ubuntu/example/BackendRest.dll
Restart=always
RestartSec=10 # Restart service after 10 seconds if dotnet service crashes
SyslogIdentifier=offershare-web-app
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=ASPNETCORE_HTTPS_PORT=5001
Environment=ASPNETCORE_URLS=https://localhost:5001

[Install]
WantedBy=multi-user.target

任何人都可以指出我哪里出错了有点新的 nginx 东西而另一个项目当使用普通 iis 托管时一切正常。

我不知道 CORS header 是否可以从 .NET 应用程序本身添加,或者是哪种方法更好,但是要通过 nginx 添加它们,您可以使用以下方法:

server {
    ...
    location / {
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin '*';
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
            add_header Content-Type text/plain;
            add_header Content-Length 0;
            return 204;
        }
        ... # your current configuration here
        add_header Access-Control-Allow-Origin '*';
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    }
}

如果您需要支持其他方法(PUTDELETE 等),请将它们添加到方法列表中。

更新

如果您的 XMLHttpRequest 正在 with credentials, you cannot use * 作为 Access-Control-Allow-Origin header 值。对于这种情况,您可以将 Access-Control-Allow-Origin 动态设置为请求的 Origin header 值:

server {
    ...
    location / {
        if ($request_method = OPTIONS) {
            add_header Access-Control-Allow-Origin $http_origin;
            add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
            add_header Content-Type text/plain;
            add_header Content-Length 0;
            return 204;
        }
        ... # your current configuration here
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    }
}

h 有同样的问题:不添加 header 到 nginx 可以解决它:

nginx 配置:

server {
    listen        80;
    server_name   example.com;
    location / {
     
        proxy_pass         http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

将此行添加到 program.cs(.net core 6)或 startup.cs(.net core 3.1 或 5):

//CORS configuration

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

//Auth... configuration

这使得代理转发所有 headers 而不需要在 nginx 上再次添加 headers。