在.NET Core Web API中启用CORS的OPTIONS头

68

在Stackoverflow上没有找到解决方法后,我解决了这个问题,现在分享我的问题以及答案。

在我的.NET Core Web Api应用程序中使用AddCors启用跨域策略后,从浏览器仍然无法工作。这是因为浏览器,包括Chrome和Firefox,将首先发送一个OPTIONS请求,而我的应用程序只会响应204 No Content。


有没有特定的场景会导致这种情况失败?如果是“任何使用CORS的Chrome / FF浏览器都会一直失败”,那么框架为什么还没有覆盖到呢?看起来这将是一个相当大的遗漏。 - ssmith
我同意。然而,这就是现状。该框架将允许您使用内置功能进行CORS,但它不处理OPTIONS调用,而这是浏览器从跨域API调用的正常使用所需的。但是,您可以通过进行更简单的调用来避免它,例如将类型设置为text/plain和其他一些内容。然后浏览器就不会首先执行OPTIONS调用。 - Niels Brinch
IIS 应该是处理这些事情的人,因此在 2017 年 11 月之后阅读此内容的任何人都应该使用 IIS CORS 模块,https://blogs.iis.net/iisteam/introducing-iis-cors-1-0 - Lex Li
是的,在Azure应用服务上,似乎在过去几年中,设置Azure中的CORS并允许某些域或所有域(*)已足够。当CORS未配置任何域时,上述方法将在Azure上起作用,这显然意味着Azure让应用程序自己处理CORS。 - Niels Brinch
https://dev59.com/H1gQ5IYBdhLWcg3wynGn#76279684 - Billu
13个回答

54

向您的项目中添加一个中间件类以处理OPTIONS动词。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Hosting;

namespace Web.Middlewares
{
    public class OptionsMiddleware
    {
        private readonly RequestDelegate _next;

        public OptionsMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public Task Invoke(HttpContext context)
        {
            return BeginInvoke(context);
        }

        private Task BeginInvoke(HttpContext context)
        {
            if (context.Request.Method == "OPTIONS")
            {
                context.Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)context.Request.Headers["Origin"] });
                context.Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
                context.Response.Headers.Add("Access-Control-Allow-Methods", new[] { "GET, POST, PUT, DELETE, OPTIONS" });
                context.Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
                context.Response.StatusCode = 200;
                return context.Response.WriteAsync("OK");
            }

            return _next.Invoke(context);
        }
    }

    public static class OptionsMiddlewareExtensions
    {
        public static IApplicationBuilder UseOptions(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<OptionsMiddleware>();
        }
    }
}

然后在Startup.cs文件中的Configure方法的第一行添加app.UseOptions();

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseOptions();
}

1
我做了这件事情,可以让请求到达中间件,但是它返回一个“无法访问请求的URL”的错误信息:服务可能暂时关闭或已永久移动到新的Web地址。[Chrome套接字错误]:连接被重置(对应于TCP RST)”。我做错了什么? - crackedcornjimmy
1
我不知道。为了解决问题,我会打开 Fiddler 并检查请求和响应的详细信息。 - Niels Brinch
7
+1,但需要进行调整,如果方法是OPTIONS,则不要调用_next.Invoke,调用 context.Response.WriteAsync("OK"); 后请求应该结束,因此更改 Invoke 实现为:if (context.Request.Method != "OPTIONS") { await this._next.Invoke(context); } - Amen Ayach
1
你可以使用CorsPolicyBuilder.AllowAnyOrigin()。你的回答没有任何免责声明,有人可能会找到并应用它而不理解。 - Der_Meister
3
@AmenAyach的代码中,如果条件成立,会返回context.Response.WriteAsync("OK");语句,因此在OPTIONS方法时不会到达_next.invoke(context) - Jesse de gans
显示剩余5条评论

38

我知道这个问题已经有了答案,只是想提供最新的信息以帮助其他人。

现在它已经集成到 ASP.NET Core 框架中。

只需按照 https://learn.microsoft.com/en-us/aspnet/core/security/cors 的步骤进行操作。

并替换

    app.UseCors(builder =>
   builder.WithOrigins("http://example.com"));

随着

        app.UseCors(builder =>
       builder.WithOrigins("http://example.com")
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials());

6
它有效吗?问题在于Chrome首先发送一个“OPTIONS”调用,但没有得到许可,因此真正的调用从Chrome浏览器中未能进行。这就是“AllowAnyMethod”解决的问题吗?编辑:我现在看到这正是您提供的文章所解决的问题! - Niels Brinch
1
请注意:如果您不想允许任何标头或方法,您可以使用WithHeaders/WithMethods方法。 - Gerrit-K
2
通过这样做,我会在响应状态码中得到“204无内容”。 - Nitin Sawant
当我尝试这样做时,我也会收到“204无内容”的响应。 - Mark Entingh
3
一个 204 No Content 是一个问题吗?似乎你从一个 OPTIONS 请求中应该期望的唯一信息只有在标头中。是这样吗?Mozilla 似乎也这么认为。 - Dan Narsavage
2
这需要放到 Startup.cs 中的 Configure() 方法中。 - Luke

26

这对我有效:

确保这个:

app.UseCors(builder => {
    builder.AllowAnyOrigin();
    builder.AllowAnyMethod();
    builder.AllowAnyHeader();
});

发生在这些事件之前:

app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseCookiePolicy();

记住,我们正在处理一个“管道”。cors的内容必须首先处理。

-gimzani


问题是这些设置是否在OPTIONS调用时应用。 - Niels Brinch
2
谢谢,这对我有用。我想强调cors必须在任何其他HTTP请求管道配置方法之前,就像@NielsBrinch所说的那样。 - Acemond
1
我想知道为什么IDE或服务器没有关于这些错误的警告。这些是简单的陷阱,让毫无戒心的开发人员陷入其中,并花费额外的时间来调试。我猜StackOverflow已经帮助缓解了很多开发者的抑郁情绪。 - Prince Owen

8

无需额外的中间件。如上所述,唯一需要的是在CORS配置中允许OPTIONS方法。 您可以像这里建议的那样使用AllowAnyMethod: https://dev59.com/H1gQ5IYBdhLWcg3wynGn#55764660

但更安全的做法是仅允许特定的内容,例如:

app.UseCors(builder => builder
.WithOrigins("https://localhost", "https://production.company.com") /* list of environments that will access this api */
.WithMethods("GET", "OPTIONS") /* assuming your endpoint only supports GET */
.WithHeaders("Origin", "Authorization") /* headers apart of safe-list ones that you use */
);

一些标头始终允许:

https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header

为什么你包含了OPTIONS方法? - undefined

6

AspNetCoreModuleV2 OPTIONS 问题

.Net Core 模块无法处理 OPTIONS 请求,这会导致预检请求(CORS)的问题,因此解决方案是将 OPTIONS 动词从处理中排除。这可以通过用想要的动词替换 "*"(所有动词)来实现,但需要保留 OPTIONS 动词。不用担心,OPTIONS 动词将由默认加载的 OPTIONSHandler 处理:

IIS

解决方案:修改 web.config 文件。

 <add name="aspNetCore" path="*" verb="* modules="AspNetCoreModuleV2" resourceType="Unspecified" />

把它做成这样:

<add name="aspNetCore" path="*" verb="GET,POST,PUT,DELETE" modules="AspNetCoreModuleV2" resourceType="Unspecified" />

IIS Express: 用于 Visual Studio 调试器

我尝试修改 .vs\ProjectName\config\applicationhost.config 文件,但是没有任何希望。因此,在这种特定情况下,您可以使用所选答案。


这真的解决了我的问题。非常感谢。对我来说,这是正确的解决方案,因为我正在使用托管在IIS 8.5上的dotnet core 7 API。 - Dileep KK
我在使用IIS Express时遇到了问题:对于Visual Studio调试器来说,GET方法可以正常工作,但是POST方法的预检请求却不行:/ 我尝试了以下代码:app.UseOptions(); app.UseCors(policy => policy.WithOrigins("https://localhost:44414") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials());但是我仍然收到了Response to preflight request doesn't pass access control check的错误。有什么建议吗? - undefined

1
如果您正在使用IIS和Windows身份验证以及Cors,请确保在IIS中同时启用Windows(GET / PUT / POST / DELETE / ETC)和匿名身份验证(用于OPTIONS / Preflight)。我遇到了这个问题,因为它在NPM RUN SERVE中运行良好,但在IIS中却不起作用,因为我已经禁用了匿名身份。我假设Windows身份验证足够了,事实上直到我尝试PUT / POST时才意识到存在问题。Preflight对于PUT / POST是必需的,而且显然无法与Windows身份验证一起使用。目标框架是.NET 6.0。
builder.Services.AddControllers();
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        builder =>
        {
            builder
            .WithOrigins(
                "https://myserver",
                "http://localhost:8080")
            .AllowCredentials()
            .WithHeaders(HeaderNames.ContentType, "x-custom-header")
            .AllowAnyMethod() 
            .WithExposedHeaders(HeaderNames.ContentType, "x-custom-header")
            ;
        });
});

// Compressed responses over secure connections can be controlled with the EnableForHttps option, which is disabled by default because of the security risk.
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
});

// SQL
string connectionString = builder.Configuration.GetConnectionString("WebApiDatabase");
builder.Services.AddDbContext<otscorecard_2022_webapi.Models.WebApiDbContext>(options =>
    options.UseSqlServer(connectionString)
);

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
   .AddNegotiate();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy.
    options.FallbackPolicy = options.DefaultPolicy;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseCors();  // IIS: enable both Windows (GET/PUT/POST/DELETE/ETC) and Anonymous Authentication (used for OPTIONS/PreFlight)
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseResponseCompression();
app.Run();

1

我希望允许单个方法执行此操作,而不是使用中间件来允许任何方法执行此操作。这就是我最终所做的:

手动处理“OPTIONS”方法

[HttpOptions("/find")]
public IActionResult FindOptions()
{
    Response.Headers.Add("Access-Control-Allow-Origin", new[] { (string)Request.Headers["Origin"] });
    Response.Headers.Add("Access-Control-Allow-Headers", new[] { "Origin, X-Requested-With, Content-Type, Accept" });
    Response.Headers.Add("Access-Control-Allow-Methods", new[] { "POST, OPTIONS" }); // new[] { "GET, POST, PUT, DELETE, OPTIONS" }
    Response.Headers.Add("Access-Control-Allow-Credentials", new[] { "true" });
    return NoContent();
}

[HttpPost("/find")]
public async Task<IActionResult> FindOptions([FromForm]Find_POSTModel model)
{
    AllowCrossOrigin();
    
    // your code...
}

private void AllowCrossOrigin()
{
    Uri origin = null;
    Uri.TryCreate(Request.Headers["Origin"].FirstOrDefault(), UriKind.Absolute, out origin);

    if (origin != null && IsOriginAllowed(origin))
        Response.Headers.Add("Access-Control-Allow-Origin", $"{origin.Scheme}://{origin.Host}");
}

当然,您可以按照自己的意愿实现IsOriginAllowed

private bool IsOriginAllowed(Uri origin)
{
    const string myDomain = "mydomain.com";
    const string[] allowedDomains = new []{ "example.com", "sub.example.com" };

    return 
           allowedDomains.Contains(origin.Host) 
           || origin.Host.EndsWith($".{myDomain}");
}

您可以在如何在单个端点上启用CORS以进行POST请求上找到更多详细信息。


1
您可能在IIS中阻止了OPTIONS http动词。请检查您的IIS中的“HTTP动词”选项卡中的请求过滤设置。从以下链接中显示的图像中删除已突出显示的选项即可。 IIS请求过滤

1
实际上,所有的答案都对我没用,但是我最终找出了问题所在,而且我简直不敢相信 我只是把app.UserCors("PolicyName"); 移到了app.UseAuthorization(); 之前,它就开始工作了!
我认为这可能对某些人有帮助。
services.AddCors(options =>
{
  options.AddPolicy("EnableCORS", bl =>
  {
    bl.WithOrigins(origins)
      .AllowAnyMethod()
      .AllowAnyHeader()
      .AllowCredentials()
      .Build();
  });
});


..........................
app.UseAuthentication();
app.UseCors("EnableCORS");
.....
app.UseAuthorization();

0

我想针对我的特定情况提供一个具体的答案,我在本地测试API和客户端Web应用程序。我知道这是一个晚期条目,但CORS在dot net core中已经发生了很大变化,我认为像我这样的新手可能会从完整的帖子中受益。

对我来说,出现了两个问题。

  1. CORS拒绝错误
  2. 并且Firefox上也有OPTIONS问题(我认为Chrome也会出现同样的问题)
  3. 我的API正在运行HTTPS
  4. Web应用程序没有HTTPS
  5. 两者都在本地运行,再次提醒,以确保清晰明了。

首先,这将进入public void ConfigureServices(IServiceCollection services)

        //lets add some CORS stuff 
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(builder => {
                builder.WithOrigins("http://localhost:3000",
                                    "http://www.contoso.com");
                builder.AllowAnyMethod();
                builder.AllowAnyHeader();
                builder.AllowCredentials();
            });
        });

然后,这个函数进入了public void Configure(IApplicationBuilder app, IWebHostEnvironment env)。

  app.UseCors();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接