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';
}
}
如果您需要支持其他方法(PUT
、DELETE
等),请将它们添加到方法列表中。
更新
如果您的 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。
从浏览器访问时出现此错误
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';
}
}
如果您需要支持其他方法(PUT
、DELETE
等),请将它们添加到方法列表中。
更新
如果您的 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。