作者: Rick Anderson Kirk Larkin

本文介绍如何在 ASP.NET Core 应用中启用 CORS。

浏览器安全性可防止网页向不处理网页的域发送请求。 此限制称为同域策略。 同域策略可防止恶意站点从另一站点读取敏感数据。 有时,你可能希望允许其他网站向自己的应用发出跨源请求。 有关详细信息,请参阅 Mozilla CORS 文章

跨源资源共享 (CORS):

  • 是一种 W3C 标准,允许服务器放宽同源策略。
  • 不是安全功能,CORS 放松了安全限制。 允许 CORS 并不会使 API 更安全。 有关详细信息,请参阅 CORS 的工作原理
  • 允许服务器显式允许某些跨源请求,同时拒绝其他请求。
  • 比早期技术(如 JSONP )更安全、更灵活。
  • 查看或下载示例代码 如何下载

    如果两个 URL 具有相同的方案、主机和端口 ( RFC 6454 ),则它们同源。

    这两个 URL 同源:

  • https://example.com/foo.html
  • https://example.com/bar.html
  • 这些 URL 的源与前两个 URL 不同:

  • https://example.net :不同的域
  • https://www.example.com/foo.html :不同的子域
  • http://example.com/foo.html :不同的方案
  • https://example.com:9000/foo.html :不同的端口
  • 启用 CORS

    有三种方法可以启用 CORS:

  • 在使用 命名策略 默认策略 的中间件中。
  • 使用 终结点路由
  • 使用 [EnableCors] 属性。
  • [EnableCors] 属性与命名策略一起使用可在限制支持 CORS 的终结点方面提供最佳控制。

    UseCors 必须按正确的顺序调用。 有关详细信息,请参阅 中间件顺序 。 例如,使用 UseResponseCaching 时,必须在 UseResponseCaching 之前调用 UseCors

    以下各部分详细介绍了每种方法。

    具有命名策略和中间件的 CORS

    CORS 中间件处理跨源请求。 以下代码将 CORS 策略应用于具有指定源的所有应用终结点:

    var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
                          policy  =>
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com");
    // services.AddResponseCaching();
    builder.Services.AddControllers();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors(MyAllowSpecificOrigins);
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    前面的代码:

  • 将策略名称设置为 _myAllowSpecificOrigins。 策略名称是任意的。
  • 调用 UseCors 扩展方法并指定 _myAllowSpecificOrigins CORS 策略。 UseCors 添加 CORS 中间件。 对 UseCors 的调用必须放在 UseRouting 之后,但在 UseAuthorization 之前。 有关详细信息,请参阅中间件顺序
  • 使用 lambda 表达式调用 AddCors。 lambda 采用 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如 WithOrigins
  • 为所有控制器终结点启用 _myAllowSpecificOrigins CORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由
  • 使用响应缓存中间件时,请在 UseResponseCaching 之前调用 UseCors
  • 对于终结点路由,CORS 中间件必须配置为在对 UseRoutingUseEndpoints 的调用之间执行。

    有关测试与前面代码类似的代码的说明,请参阅测试 CORS

    AddCors 方法调用将 CORS 服务添加到应用的服务容器:

    var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
                          policy  =>
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com");
    // services.AddResponseCaching();
    builder.Services.AddControllers();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors(MyAllowSpecificOrigins);
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    有关详细信息,请参阅本文档中的 CORS 策略选项

    可以链接 CorsPolicyBuilder 方法,如以下代码所示:

    var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(MyAllowSpecificOrigins,
                              policy =>
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com")
                                                      .AllowAnyHeader()
                                                      .AllowAnyMethod();
    builder.Services.AddControllers();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors(MyAllowSpecificOrigins);
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    注意:指定的 URL 不能包含尾部斜杠 (/)。 如果 URL 以 / 结尾,则比较返回 false,并且不返回任何标头。

    UseCors 必须放在 之后 UseRouting 和之前 UseAuthorization。 这是为了确保 CORS 标头包含在已授权和未经授权的调用的响应中。

    UseCors 和 UseStaticFiles 顺序

    通常, UseStaticFiles 在 之前 UseCors调用 。 使用 JavaScript 跨站点检索静态文件的应用必须在 之前UseStaticFiles调用 UseCors

    具有默认策略和中间件的 CORS

    以下突出显示的代码启用默认 CORS 策略:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddDefaultPolicy(
            policy =>
                policy.WithOrigins("http://example.com",
                                    "http://www.contoso.com");
    builder.Services.AddControllers();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    前面的代码将默认 CORS 策略应用于所有控制器终结点。

    通过终结点路由启用 Cors

    对于终结点路由,可以使用 RequireCors 扩展方法集基于每个终结点启用 CORS:

    var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
                          policy =>
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com");
    builder.Services.AddControllers();
    builder.Services.AddRazorPages();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
        endpoints.MapGet("/echo",
            context => context.Response.WriteAsync("echo"))
            .RequireCors(MyAllowSpecificOrigins);
        endpoints.MapControllers()
                 .RequireCors(MyAllowSpecificOrigins);
        endpoints.MapGet("/echo2",
            context => context.Response.WriteAsync("echo2"));
        endpoints.MapRazorPages();
    app.Run();
    

    在上述代码中:

  • app.UseCors 启用 CORS 中间件。 由于尚未配置默认策略,因此单独的 app.UseCors() 不会启用 CORS。
  • /echo 和控制器终结点允许使用指定策略的跨源请求。
  • 和 Pages 终结点不允许跨域请求,因为未指定默认策略。Razor/echo2
  • [DisableCors] 属性不会禁用终结点路由已使用 RequireCors 启用的 CORS。

    在 ASP.NET Core 7.0 中[EnableCors],特性必须传递参数,或者 ASP0023 警告是从路由上的不明确匹配生成的。 ASP.NET Core 8.0 及更高版本不会生成ASP0023警告。

    [Route("api/[controller]")]
    [ApiController]
    public class TodoItems2Controller : ControllerBase
        // OPTIONS: api/TodoItems2/5
        [HttpOptions("{id}")]
        public IActionResult PreflightRoute(int id)
            return NoContent();
        // OPTIONS: api/TodoItems2 
        [HttpOptions]
        public IActionResult PreflightRoute()
            return NoContent();
        [HttpPut("{id}")]
        public IActionResult PutTodoItem(int id)
            if (id < 1)
                return BadRequest();
            return ControllerContext.MyDisplayRouteInfo(id);
        // [EnableCors] // Not needed as OPTIONS path provided.
        [HttpDelete("{id}")]
        public IActionResult MyDelete(int id) =>
            ControllerContext.MyDisplayRouteInfo(id);
        // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
        //                  An HTTP request that matches multiple routes results in an ambiguous
        //                  match error.
        [EnableCors("MyPolicy")] // Required for this path.
        [HttpGet]
        public IActionResult GetTodoItems() =>
            ControllerContext.MyDisplayRouteInfo();
        [HttpGet("{action}")]
        public IActionResult GetTodoItems2() =>
            ControllerContext.MyDisplayRouteInfo();
        [EnableCors("MyPolicy")]  // Required for this path.
        [HttpDelete("{action}/{id}")]
        public IActionResult MyDelete2(int id) =>
            ControllerContext.MyDisplayRouteInfo(id);
    

    有关测试代码的说明,请参阅 使用 [EnableCors] 属性和 RequireCors 方法测试 CORS ,如前所述。

    使用属性启用 CORS

    使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。

    [EnableCors] 属性提供了全局应用 CORS 的替代方法。 [EnableCors] 属性为所选终结点(而不是所有终结点)启用 CORS:

  • [EnableCors] 指定默认策略。
  • [EnableCors("{Policy String}")] 指定命名策略。
  • [EnableCors] 属性可应用于:

  • Razor Page PageModel
  • 控制器操作方法
  • 可将不同的策略应用于具有 [EnableCors] 属性的控制器、页面模型或操作方法。 将 [EnableCors] 属性应用于控制器、页面模型或操作方法,并在中间件中启用 CORS 时, 将应用这两种 策略。 建议不要合并策略。 使用 [EnableCors]属性或中间件,不能同时位于同一应用中。

    以下代码对每种方法应用不同的策略:

    [Route("api/[controller]")]
    [ApiController]
    public class WidgetController : ControllerBase
        // GET api/values
        [EnableCors("AnotherPolicy")]
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
            return new string[] { "green widget", "red widget" };
        // GET api/values/5
        [EnableCors("Policy1")]
        [HttpGet("{id}")]
        public ActionResult<string> Get(int id)
            return id switch
                1 => "green widget",
                2 => "red widget",
                _ => NotFound(),
    

    以下代码创建两个 CORS 策略:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy("Policy1",
            policy =>
                policy.WithOrigins("http://example.com",
                                    "http://www.contoso.com");
        options.AddPolicy("AnotherPolicy",
            policy =>
                policy.WithOrigins("http://www.contoso.com")
                                    .AllowAnyHeader()
                                    .AllowAnyMethod();
    builder.Services.AddControllers();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.MapControllers();
    app.Run();
    

    为了最精细地控制 CORS 请求的限制:

  • [EnableCors("MyPolicy")] 与命名策略一起使用。
  • 不要定义默认策略。
  • 不要使用终结点路由
  • 下一部分中的代码符合前面的列表。

    有关测试与前面代码类似的代码的说明,请参阅测试 CORS

    禁用 CORS

    [DisableCors] 属性不会禁用终结点路由已启用的 CORS。

    以下代码定义 CORS 策略 "MyPolicy"

    var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: "MyPolicy",
            policy =>
                policy.WithOrigins("http://example.com",
                                    "http://www.contoso.com")
                        .WithMethods("PUT", "DELETE", "GET");
    builder.Services.AddControllers();
    builder.Services.AddRazorPages();
    var app = builder.Build();
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => {
        endpoints.MapControllers();
        endpoints.MapRazorPages();
    app.Run();
    

    以下代码为 GetValues2 操作禁用 CORS:

    [EnableCors("MyPolicy")]
    [Route("api/[controller]")]
    [ApiController]
    public class ValuesController : ControllerBase
        // GET api/values
        [HttpGet]
        public IActionResult Get() =>
            ControllerContext.MyDisplayRouteInfo();
        // GET api/values/5
        [HttpGet("{id}")]
        public IActionResult Get(int id) =>
            ControllerContext.MyDisplayRouteInfo(id);
        // PUT api/values/5
        [HttpPut("{id}")]
        public IActionResult Put(int id) =>
            ControllerContext.MyDisplayRouteInfo(id);
        // GET: api/values/GetValues2
        [DisableCors]
        [HttpGet("{action}")]
        public IActionResult GetValues2() =>
            ControllerContext.MyDisplayRouteInfo();
    

    前面的代码:

  • 不通过终结点路由启用 CORS。
  • 不定义默认 CORS 策略
  • 使用 [EnableCors("MyPolicy")] 为控制器启用 "MyPolicy" CORS 策略。
  • GetValues2 方法禁用 CORS。
  • 有关测试前面代码的说明,请参阅测试 CORS

    CORS 策略选项

    本部分介绍可以在 CORS 策略中设置的各种选项:

  • 设置允许的源
  • 设置允许的 HTTP 方法
  • 设置允许的请求头
  • 设置公开的响应头
  • 跨源请求中的凭据
  • 设置预检过期时间
  • AddPolicyProgram.cs 中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。

    设置允许的源

    AllowAnyOrigin:允许具有任何方案(httphttps)的所有源的 CORS 请求。 AllowAnyOrigin 不安全,因为 任何网站 都可以向应用发出跨域请求。

    指定 AllowAnyOriginAllowCredentials 是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,CORS 服务会返回无效的 CORS 响应。

    AllowAnyOrigin 影响预检请求和 Access-Control-Allow-Origin 头。 有关详细信息,请参阅预检请求部分。

    SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 IsOriginAllowed 属性设置为一个函数,当计算是否允许源时,此函数允许源匹配已配置的通配符域。

    var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
            policy =>
                policy.WithOrigins("https://*.example.com")
                    .SetIsOriginAllowedToAllowWildcardSubdomains();
    builder.Services.AddControllers();
    var app = builder.Build();
    

    设置允许的 HTTP 方法

    AllowAnyMethod:

  • 允许任何 HTTP 方法:
  • 影响预检请求和 Access-Control-Allow-Methods 头。 有关详细信息,请参阅预检请求部分。
  • 设置允许的请求头

    要允许在称为作者请求头的 CORS 请求中发送特定头,请调用 WithHeaders 并指定允许的头:

    using Microsoft.Net.Http.Headers;
    var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
           policy =>
               policy.WithOrigins("http://example.com")
                      .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    builder.Services.AddControllers();
    var app = builder.Build();
    

    要允许所有 作者请求头,请调用 AllowAnyHeader

    var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy(name: MyAllowSpecificOrigins,
            policy =>
                policy.WithOrigins("https://*.example.com")
                       .AllowAnyHeader();
    builder.Services.AddControllers();
    var app = builder.Build();
    

    AllowAnyHeader 影响预检请求和 Access-Control-Request-Headers 头。 有关详细信息,请参阅预检请求部分。

    仅当在 Access-Control-Request-Headers 中发送的头与 WithHeaders 中所述的头完全匹配时,才能与 WithHeaders 指定的特定头匹配 CORS 中间件策略。

    例如,考虑按如下方式配置的应用:

    app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
    

    CORS 中间件拒绝具有以下请求头的预检请求,因为 Content-Language (HeaderNames.ContentLanguage) 未列在 WithHeaders 中:

    Access-Control-Request-Headers: Cache-Control, Content-Language
    

    应用返回 200 OK 响应,但不发回 CORS 头。 因此,浏览器不会尝试跨源请求。

    设置公开的响应头

    默认情况下,浏览器不会向应用公开所有响应头。 有关详细信息,请参阅 W3C 跨源资源共享(术语):简单响应头

    默认情况下可用的响应头包括:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma
  • CORS 规范将这些头称为简单响应头。 要使其他头可用于应用,请调用 WithExposedHeaders

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy("MyExposeResponseHeadersPolicy",
            policy =>
                policy.WithOrigins("https://*.example.com")
                       .WithExposedHeaders("x-custom-header");
    builder.Services.AddControllers();
    var app = builder.Build();
    

    跨源请求中的凭据

    凭据需要在 CORS 请求中进行特殊处理。 默认情况下,浏览器不会使用跨源域请求发送凭据。 凭据包括 cookie 和 HTTP 身份验证方案。 要使用跨源请求发送凭据,客户端必须将 XMLHttpRequest.withCredentials 设置为 true

    直接使用 XMLHttpRequest

    var xhr = new XMLHttpRequest();
    xhr.open('get', 'https://www.example.com/api/test');
    xhr.withCredentials = true;
    

    使用 jQuery:

    $.ajax({
      type: 'get',
      url: 'https://www.example.com/api/test',
      xhrFields: {
        withCredentials: true
    

    使用 Fetch API

    fetch('https://www.example.com/api/test', {
        credentials: 'include'
    

    服务器必须允许凭据。 要允许跨源凭据,请调用 AllowCredentials

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddCors(options =>
        options.AddPolicy("MyMyAllowCredentialsPolicy",
            policy =>
                policy.WithOrigins("http://example.com")
                       .AllowCredentials();
    builder.Services.AddControllers();
    var app = builder.Build();
    

    HTTP 响应包含一个 Access-Control-Allow-Credentials 头,它告诉浏览器服务器允许跨源请求的凭据。

    如果浏览器发送凭据,但响应不包含有效的 Access-Control-Allow-Credentials 头,则浏览器不会向应用公开响应,而且跨源请求会失败。

    允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。