如何在ASP.net Core WebAPI中启用CORS

328

我想做什么

我有一个后端ASP.Net Core Web API,托管在Azure免费计划上(Add default security headers in .Net Core)。

我还有一个客户网站,我希望让它消耗该API。客户端应用程序将不会托管在Azure上,而是将托管在Github Pages或我可以访问的另一个Web托管服务上。因此,域名不匹配。

为了解决这个问题,我需要在Web API端启用CORS,但是我已经尝试了几个小时的所有方法,但它仍然无法工作。

客户端设置方式 这只是一个简单的React.js客户端。我通过Jquery中的AJAX调用API。React网站可以正常工作,所以我知道不是那个问题。我已经确认了Jquery API调用的有效性。以下是我的调用方式:

    var apiUrl = "http://andrewgodfroyportfolioapi.azurewebsites.net/api/Authentication";
    //alert(username + "|" + password + "|" + apiUrl);
    $.ajax({
        url: apiUrl,
        type: "POST",
        data: {
            username: username,
            password: password
        },
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        success: function (response) {
            var authenticatedUser = JSON.parse(response);
            //alert("Data Loaded: " + authenticatedUser);
            if (onComplete != null) {
                onComplete(authenticatedUser);
            }
        },
        error: function (xhr, status, error) {
            //alert(xhr.responseText);
            if (onComplete != null) {
                onComplete(xhr.responseText);
            }
        }
    });

我尝试过什么


尝试1 - “正确”的方式

https://learn.microsoft.com/en-us/aspnet/core/security/cors

我完全按照微软网站上的教程进行了操作,尝试了在Startup.cs中启用全局选项、在每个控制器上设置以及在每个Action上尝试。

按照这种方法,跨域可以工作,但仅限于单个控制器上的单个Action(POST到AccountController)。对于其他所有内容,Microsoft.AspNetCore.Cors中间件都拒绝设置标头。

我通过NUGET安装了Microsoft.AspNetCore.Cors,版本为1.1.2

以下是我在Startup.cs中的设置方式

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Add Cors
        services.AddCors(o => o.AddPolicy("MyPolicy", builder =>
        {
            builder.AllowAnyOrigin()
                   .AllowAnyMethod()
                   .AllowAnyHeader();
        }));

        // Add framework services.
        services.AddMvc();
        services.Configure<MvcOptions>(options =>
        {
            options.Filters.Add(new CorsAuthorizationFilterFactory("MyPolicy"));
        });

        ...
        ...
        ...
    }

    // This method gets called by the runtime. Use this method to configure 
    //the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env,
    ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        // Enable Cors
        app.UseCors("MyPolicy");

        //app.UseMvcWithDefaultRoute();
        app.UseMvc();
        
        ...
        ...
        ...
    }

正如您所看到的,我正在按照指示进行操作。我在 MVC 之前两次添加了 Cors,在那不起作用时,我尝试将 [EnableCors("MyPolicy")] 放在每个控制器上。

[Route("api/[controller]")]
[EnableCors("MyPolicy")]
public class AdminController : Controller

尝试2 - 暴力破解

https://andrewlock.net/adding-default-security-headers-in-asp-net-core/

在之前的尝试中,我试了几个小时后,决定尝试通过手动设置头部来进行暴力破解,强制它们在每个响应上运行。我按照这篇关于如何手动添加头部到每个响应的教程进行操作。

这些是我添加的头部:

.AddCustomHeader("Access-Control-Allow-Origin", "*")
.AddCustomHeader("Access-Control-Allow-Methods", "*")
.AddCustomHeader("Access-Control-Allow-Headers", "*")
.AddCustomHeader("Access-Control-Max-Age", "86400")

这些是我尝试过但失败的其他标头

.AddCustomHeader("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE")
.AddCustomHeader("Access-Control-Allow-Headers", "content-type, accept, X-PINGOTHER")
.AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Host, User-Agent, Accept, Accept: application/json, application/json, Accept-Language, Accept-Encoding, Access-Control-Request-Method, Access-Control-Request-Headers, Origin, Connection, Content-Type, Content-Type: application/json, Authorization, Connection, Origin, Referer")

使用这种方法,跨站点标头被正确应用,并显示在我的开发人员控制台和Postman中。然而问题在于,虽然它通过了Access-Control-Allow-Origin检查,但Web浏览器会在(我认为是)Access-Control-Allow-Headers上抛出一些问题,指出415(不支持的媒体类型)。因此,暴力方法也不起作用。

最后

有没有人已经让这个工作了并且可以帮帮我,或者只是能够指导我方向?


编辑

为了使API调用成功,我不得不停止使用JQuery并转而使用纯Javascript的XMLHttpRequest格式。

尝试1

我成功地让Microsoft.AspNetCore.Cors按照MindingData的回答工作,除了在Configure方法中将app.UseCors放在app.UseMvc之前。

此外,当与Javascript API解决方案混合使用时,options.AllowAnyOrigin()用于通配符支持也开始工作了。

尝试2

因此,我设法让尝试2(强制执行)工作……唯一的例外是Access-Control-Allow-Origin的通配符不起作用,因此我必须手动设置具有访问权限的域。

显然,这并不理想,因为我只希望这个WebAPI向所有人敞开大门,但至少它在一个单独的网站上对我有效,这意味着它是一个开始。

app.UseSecurityHeadersMiddleware(new SecurityHeadersBuilder()
    .AddDefaultSecurePolicy()
    .AddCustomHeader("Access-Control-Allow-Origin", "http://localhost:3000")
    .AddCustomHeader("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT, PATCH, DELETE")
    .AddCustomHeader("Access-Control-Allow-Headers", "X-PINGOTHER, Content-Type, Authorization"));

3
针对您的“415(不支持的媒体类型)”问题,请将“Content-Type”请求头设置为“application/json”。 - Technetium
15
感谢花时间撰写如此详细的问题。 - user1007074
2
如果您正在使用Postman进行测试,请确保将请求头中的Origin设置为*或其他内容,然后尝试#1应该可以正常工作。如果没有此标头,则响应标头中将不会返回Access-Control-Allow-Origin。 - tala9999
是下面关于XMLHttpRequest的评论让我明白了,谢谢! - Kirk Hawley
我也在同样的问题上苦苦挣扎,从昨天开始就一直在尝试使用我的自定义中间件进行暴力破解。这真是让人头疼的事情。 - Gilbert
很高兴知道其他人也花了几个小时在这上面 - 我什么都做不了,甚至连一点点都不行,我马上要尝试你的暴力破解版本。 - PandaWood
38个回答

336

因为您的CORS政策非常简单(允许来自XXX域的所有请求),所以您不需要使它变得复杂。首先尝试进行以下操作(一个非常基本的CORS实现)。

如果您还没有安装CORS nuget包,请安装它。

Install-Package Microsoft.AspNetCore.Cors
在你的 startup.cs 文件的 ConfigureServices 方法中,添加 CORS 服务。
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(); // Make sure you call this previous to AddMvc
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

然后在startup.cs文件的Configure方法中添加如下代码:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    // Make sure you call this before calling app.UseMvc()
    app.UseCors(
        options => options.WithOrigins("http://example.com").AllowAnyMethod()
    );

    app.UseMvc();
}

现在可以试试了。当您需要为不同的操作(例如不同的主机或不同的标头)使用不同的策略时,请使用策略。对于您的简单示例,您确实不需要它。从这个简单的示例开始,并根据需要进行微调。

进一步阅读:http://dotnetcoretutorials.com/2017/01/03/enabling-cors-asp-net-core/


10
这很可能行不通,当您在 app.UseMvc() 之后 注册 app.UseCors。中间件按照注册顺序执行。 - Tseng
31
似乎在Configure方法中在app.UseMvc之前使用app.UseCors可以起作用。由于某种原因,顺序似乎很重要。 - MrClan
4
为使这一切工作,我不得不启用 options.DisableHttpsRequirement();。似乎在启用了https的跨域设置后并没有生效。 - Michael Brown
5
为什么在纯WebAPI项目中需要添加UseMVC()?加载所有MVC内容以使CORS工作是否必要? - Giox
显示剩余8条评论

297
  • ConfigureServices中添加 services.AddCors(); 在 services.AddMvc(); 之前。

  • Configure中添加 UseCors。

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

主要的关键点是在app.UseMvc()之前添加app.UseCors

确保在MVC之前声明CORS功能,以便中间件在MVC管道获取控制并终止请求之前启动。

在上述方法可行后,您可以更改配置以接受特定的ORIGIN来调用API,并避免将API开放给任何人。

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options => options.AddPolicy("ApiCorsPolicy", builder =>
    {
        builder.WithOrigins("http://localhost:4200").AllowAnyMethod().AllowAnyHeader();
    }));

    services.AddMvc();
}
在configure方法中告诉CORS使用你刚创建的策略:
app.UseCors("ApiCorsPolicy");
app.UseMvc();

我刚刚在这个主题上找到了一篇简洁的文章 - https://dzone.com/articles/cors-in-net-core-net-core-security-part-vi


1
这对我有用。https://www.codeproject.com/Articles/1150023/Enable-Cross-origin-Resource-Sharing-CORS-in-ASP-N - hubert17
32
这篇文章应该得到更多的点赞,因为它是一个很好的“起点”。在我编程25年的经验中,知道如何打开水闸以确保它真正“工作”,然后根据需要关闭/加固它们总是很有用的。 - Indy-Jones
4
提一下,在Configure()中顺序很重要,但在ConfigureServices()中顺序并不重要。 - Felix K.
1
我使用了Further Reader中提供的链接,按照其中的步骤解决了这个错误。我不确定这些更改应该放在哪里(我想是API)。链接确认它们应该放在API中。感谢你的帮助。我之前一直在为这个错误发愁。 - Richard
2
FYI——CORS规范还指出,如果存在Access-Control-Allow-Credentials标头,则将起源设置为“*”(所有起源)无效。这意味着您不能像上面那样使用AllowCredentials()AllowAnyOrigin()。要使用AllowCredentials(),您需要设置WithOrigins()。https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.2 - Nick De Beer
显示剩余5条评论

47
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {      
       app.UseCors(builder => builder
                .AllowAnyHeader()
                .AllowAnyMethod()
                .SetIsOriginAllowed((host) => true)
                .AllowCredentials()
            );
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();
    }

2
.SetIsOriginAllowed((host) => true) 对我解决了这个问题。 - JensB
哇,我完全预料到其他答案都会在这个只有3票的小答案之前起作用。但是我仔细地尝试了每一个,...突然心血来潮,我选择了你的答案,它奏效了。谢谢你。 - roberto tomás
这是我在 .NET Core 3.1 上使用的解决方案,几乎可以允许任何东西。 - JustinHui
谢谢。这是唯一对我有效的解决方案。[code]services.AddCors();[/code]的顺序也很重要。 - Saurabh Rana
这也是唯一一个对我有效的。SignalR Asp.Net Core 3.1 - smithygreg
根据技术文档,它说SetIsOriginAllowed“为基础策略设置指定的isOriginAllowed”。请有人详细说明该方法的作用吗?谢谢。 - crazyTech

41

我创建了自己的中间件类,它对我很有效,我认为 .net core 的中间件类有些问题。

public class CorsMiddleware
{
    private readonly RequestDelegate _next;

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

    public Task Invoke(HttpContext httpContext)
    {
        httpContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
        httpContext.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
        httpContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
        httpContext.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
        return _next(httpContext);
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class CorsMiddlewareExtensions
{
    public static IApplicationBuilder UseCorsMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<CorsMiddleware>();
    }
}

并且在startup.cs中以这种方式使用它。

app.UseCorsMiddleware();

非常优雅的运行Access-Control-Allow-Origin的方式。 - Artur Poniedziałek
这适用于WebAPI和MVC,没有任何依赖项,谢谢! - Joe
3
我对此也持怀疑态度,但它对我有效。我尝试了互联网上可以找到的基本上所有其他方法来实现这个目标,但无论如何服务器都不会回应访问头信息。这个方法非常有效。我正在运行aspnetcore 2.1。 - Jordan Ryder
1
只有在客户端请求中包含“Origin”头时,您才应该返回cors标头。在原始的CospMiddleware中,它看起来像这样:if (!context.Request.Headers.ContainsKey(CorsConstants.Origin)) return this._next(context); - Andrei Prigorshnev
2
可能是 ".net core 中间件类出了问题",因为你在使用 curl 或类似工具测试时没有添加 "Origin" 标头。当你在 js 代码中发起请求时,浏览器会自动添加此标头。 - Andrei Prigorshnev
在此处查看更多详细信息 developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS#Simple_requests - Andrei Prigorshnev

24

我为这个问题挣扎了好几天。

最终,我通过将 app.UseCors(CORS_POLICY); 移到 Configure()顶部来使其正常工作。

https://weblog.west-wind.com/posts/2016/sep/26/aspnet-core-and-cors-gotchas

请确保在 MVC 之前声明 CORS 功能,因为头文件必须在 MVC 完成请求之前被应用。

<= 即使我的应用程序没有调用 UseMVC(),将 UseCors() 移动到顶部也解决了问题

另外:

  • Microsoft.AspNetCore.Cors 在 .Net Core 2 及以下版本中曾是必需的 NuGet 包;在 .Net Core 3 及更高版本中,它现在自动成为 Microsoft.AspNetCore 的一部分。
  • builder.AllowAnyOrigin().AllowCredentials() 在 .Net Core 3 及更高版本中现在是互斥的 CORS 选项。
  • CORS 策略似乎要求 Angular 使用 https 调用服务器。无论 .Net Core 服务器的 CORS 配置如何,http URL 都似乎会导致 CORS 错误。例如,http://localhost:52774/api/Contacts 会产生 CORS 错误;只需将 URL 更改为 https://localhost:44333/api/Contacts 即可解决。

额外说明:

在我的情况中,CORS 无法正常工作,直到我将 app.UseCors() 移动到 app.UseEndpoints(endpoints => endpoints.MapControllers()) 上面。

6
如果您使用的是Net Core 3,那么这个应该是答案。感谢您挽救了我的生命! - Canada Wan
4
使用终结点路由时,CORS中间件必须配置在调用UseRouting和UseEndpoints之间。配置不正确将导致中间件无法正常运行。 - Mark Schultheiss
1
使用端点路由时,CORS中间件必须在调用UseRouting和UseEndpoints之间进行配置。配置不正确将导致中间件无法正常运行。 - Mark Schultheiss
1
你提到使用https URL而不是http的建议对我有用。我已经被困惑了好几天,试图解决这个问题。谢谢! - PT2550
很高兴能帮上忙 :) - FoggyDay

22

根据MindingData的回答,在我的情况下,只有get请求能够正常工作。对于其他类型的请求,您需要编写:

app.UseCors(corsPolicyBuilder =>
   corsPolicyBuilder.WithOrigins("http://localhost:3000")
  .AllowAnyMethod()
  .AllowAnyHeader()
);

别忘了添加.AllowAnyHeader()


1
同意Towhid的看法,需要AllowAnyHeader()。这将允许服务器接收OPTIONS请求,如果请求HEADER缺少某些内容的话。 - Rivon
".AllowAnyHeader()对我很有帮助,我遇到了预检响应的问题。" - takaz

17

针对 .NET CORE 3.1

在我的情况下,我在添加 跨来源资源共享中间件 前使用了 https重定向,通过改变它们的顺序来解决问题。

我的意思是:

将这个改成:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {

      ...
        
        app.UseHttpsRedirection();  

        app.UseCors(x => x
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

      ...

     }

变为:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {

      ...
        
        app.UseCors(x => x
            .AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader());

        app.UseHttpsRedirection(); 

      ...

     }

顺便提一下,在生产环境中允许来自任何源和方法的请求可能不是一个好主意,你应该在生产环境中编写自己的CORS策略。


16

对于 .Net 6

var builder = WebApplication.CreateBuilder(args);
var apiCorsPolicy = "ApiCorsPolicy";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: apiCorsPolicy,
                      builder =>
                      {
                          builder.WithOrigins("http://localhost:4200", "https://localhost:4200")
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();
                            //.WithMethods("OPTIONS", "GET");
                      });
});

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

var app = builder.Build();
app.UseHttpsRedirection();

app.UseCors(apiCorsPolicy);

app.UseAuthorization();
app.MapControllers();
app.Run();

这里有更多的例子


对于其他不知道哪些文件是做什么的 .Net 新手,这里有一个提示,这个文件应该放在 /<YourProjectName>/Program.cs(至少在遵循 Microsoft 网站上 SignalR 示例时是这样)。 - Magnus
1
然而,在您的控制器中添加[EnableCors]之前,这不会“启动”。 在此之前,CORS无效。 - Fandango68
@Fandango68,如果您正确设置了它,那就不是真的。除非您只想为特定控制器启用它,或者您正在使用每个路由的端点。请查看此链接:https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-7.0#enable-cors-with-attributes - curiousBoy

13

针对user8266077答案,为了满足我的使用情况,我发现我仍然需要在.NET Core 2.1-preview中为预检请求提供OPTIONS响应:

// https://dev59.com/Puk5XIcBkEYKwwoY_Ox3#45844400
public class CorsMiddleware
{
  private readonly RequestDelegate _next;

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

  public async Task Invoke(HttpContext context)
  {
    context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
    context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
    // Added "Accept-Encoding" to this list
    context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Accept-Encoding, Content-Length, Content-MD5, Date, X-Api-Version, X-File-Name");
    context.Response.Headers.Add("Access-Control-Allow-Methods", "POST,GET,PUT,PATCH,DELETE,OPTIONS");
    // New Code Starts here
    if (context.Request.Method == "OPTIONS")
    {
      context.Response.StatusCode = (int)HttpStatusCode.OK;
      await context.Response.WriteAsync(string.Empty);
    }
    // New Code Ends here

    await _next(context);
  }
}

然后在 Startup.cs 中像这样启用中间件

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
  app.UseMiddleware(typeof(CorsMiddleware));
  // ... other middleware inclusion such as ErrorHandling, Caching, etc
  app.UseMvc();
}

2
我建议这样添加中间件:app.Use<CorsMiddleware>(); - Albert221
你可以用以下代码替换这两行: context.Response.StatusCode = (int)HttpStatusCode.OK; await context.Response.WriteAsync(string.Empty); 使用简单的代码: return; - Hayha
1
进一步扩展@user8266077的答案:请注意,如果由于某些其他原因请求失败,此中间件将抛出异常并且标头将不会设置。这意味着在前端,它仍然看起来像是CORS问题,即使它是完全不同的问题。我通过捕获await _next(context)中的任何异常并在发生异常时手动设置状态代码和响应来绕过此问题。当从需要授权的反应中进行请求时,我还必须将“授权”添加到Access-Control-Allow-Headers以使预检请求工作。 - Adam

11

对于ASP.NET Core 3.1,这解决了我的问题。 https://jasonwatmore.com/post/2020/05/20/aspnet-core-api-allow-cors-requests-from-any-origin-and-with-credentials

public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors();
            services.AddControllers();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseRouting();

            // global cors policy
            app.UseCors(x => x
                .AllowAnyMethod()
                .AllowAnyHeader()
                .SetIsOriginAllowed(origin => true) // allow any origin
                .AllowCredentials()); // allow credentials

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(x => x.MapControllers());
        }
    }

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