(CORS) 启用跨域请求 ASP.NET Core

作者:Rick AndersonKirk Larkin

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

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

跨源资源共享 (CORS) :

  • 是一种 W3C 标准,可让服务器放宽相同的源策略。
  • 是一项安全功能,CORS 放宽 security。 API 不能通过允许 CORS 来更安全。 有关详细信息,请参阅 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] 属性与命名策略一起使用在限制支持 CORS 的终结点方面提供了最佳控制。

警告

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

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

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

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

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              builder =>
                              {
                                  builder.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors(MyAllowSpecificOrigins);

        // app.UseResponseCaching();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

前面的代码:

通过终结点路由,CORS 中间件 必须 配置为在对和的调用之间执行 UseRouting UseEndpoints

有关测试代码的说明,请参阅 测试 CORS ,如以上代码所示。

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

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              builder =>
                              {
                                  builder.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

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

这些 CorsPolicyBuilder 方法可以链接在一起,如以下代码所示:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
                          builder =>
                          {
                              builder.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
    });

    services.AddControllers();
}

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

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

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

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                builder =>
                {
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

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

通过终结点路由启用 Cors

使用在每个终结点上启用 CORS 不 RequireCors 支持 自动预检请求 有关详细信息,请参阅 此 GitHub 问题测试与终结点路由和 [HTTPOPTIONS] 的 CORS

使用终结点路由,可以使用一组扩展方法在每个终结点上启用 CORS RequireCors

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              builder =>
                              {
                                  builder.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        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.UseCors 启用 CORS 中间件。 由于尚未配置默认策略,因此 app.UseCors() 单独不启用 CORS。
  • /echo 控制器终结点允许使用指定策略的跨源请求。
  • /echo2 Razor Pages 终结点 不允许 跨源请求,因为未指定默认策略。

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

有关测试类似于上述代码的说明,请参阅使用终结点路由和[HttpOptions] 测试 CORS。

使用属性启用 CORS

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

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

  • [EnableCors] 指定默认策略。
  • [EnableCors("{Policy String}")] 指定命名策略。

[EnableCors]属性可应用于:

  • Razor 网页 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 策略:

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("Policy1",
                builder =>
                {
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                builder =>
                {
                    builder.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

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

  • [EnableCors("MyPolicy")] 命名策略一同使用。
  • 不要定义默认策略。
  • 请勿使用 终结点路由

下一节中的代码满足前面的列表。

有关测试代码的说明,请参阅 测试 CORS ,如以上代码所示。

禁用 CORS

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

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

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                builder =>
                {
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

以下代码禁用操作的 CORS GetValues2

[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 策略选项

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

AddPolicy 在 中调用 Startup.ConfigureServices 。 对于一些选项,首先阅读 CORS 的工作原理部分可能会有所帮助。

设置允许的来源

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

备注

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

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

SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 属性设置为一个函数,该函数允许源在评估是否允许源时匹配配置的 IsOriginAllowed 通配符域。

options.AddPolicy("MyAllowSubdomainPolicy",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

设置允许的 HTTP 方法

AllowAnyMethod:

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

设置允许的请求标头

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

options.AddPolicy("MyAllowHeadersPolicy",
    builder =>
    {
        // requires using Microsoft.Net.Http.Headers;
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

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

options.AddPolicy("MyAllowAllHeadersPolicy",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

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

WithHeaders仅当发送的标头 Access-Control-Request-Headers 与中所述的标头完全匹配时,才可以使用 CORS 中间件策略匹配指定的特定标头 WithHeaders

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

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

options.AddPolicy("MyExposeResponseHeadersPolicy",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
               .WithExposedHeaders("x-custom-header");
    });

跨域请求中的凭据

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

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
  }
});

使用 提取 API

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

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

options.AddPolicy("MyMyAllowCredentialsPolicy",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowCredentials();
    });

HTTP 响应包含 Access-Control-Allow-Credentials 标头,该标头告知浏览器服务器允许跨域请求的凭据。

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

允许跨域凭据存在安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户向应用发送已登录用户的凭据。

CORS 规范还规定,如果标头 (所有源) , "*" 则所有源 Access-Control-Allow-Credentials 的源设置无效。

预检请求

对于某些 CORS 请求,浏览器在发出实际请求之前发送其他 OPTIONS 请求。 此请求称为 预检请求。 如果满足以下所有条件 ,浏览器可以 跳过预检请求:

  • 请求方法是 GET、HEAD 或 POST。
  • 应用不会设置除 、 或 外 Accept Accept-Language Content-Language Content-Type 的请求标头 Last-Event-ID
  • 标头 Content-Type (如果已设置)具有以下值之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

针对客户端请求设置的请求标头的规则适用于应用通过调用 对象设置的 setRequestHeader XMLHttpRequest 标头。 CORS 规范调用这些标头 作者请求标头。 此规则不适用于浏览器可以设置的标头,例如 User-Agent Host 、 或 Content-Length

下面是一个示例响应,类似于本文档的"测试 CORS"部分中的 "[Put test]" 按钮的预检请求。

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

预检请求使用 HTTP OPTIONS 方法。 它可以包括以下标头:

如果预检请求被拒绝,应用将返回响应, 200 OK 但不会设置 CORS 标头。 因此,浏览器不会尝试跨域请求。 有关拒绝的预检请求的示例,请参阅本文档的 测试 CORS 部分。

使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器:

  • Firefox:跨源请求被阻止:相同的源策略不允许读取上的远程资源 https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 。 (原因: CORS 请求未成功) 。 了解详细信息
  • 基于 Chromium:派生自源 "" 的 "" 访问权限已被 https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5 https://cors3.azurewebsites.net CORS 策略阻止:响应预检请求未通过访问控制检查:请求的资源上没有 "访问控制-允许-源" 标头。 如果非跳转响应可满足需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下提取资源。

若要允许特定标头,请调用 WithHeaders

options.AddPolicy("MyAllowHeadersPolicy",
    builder =>
    {
        // requires using Microsoft.Net.Http.Headers;
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

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

options.AddPolicy("MyAllowAllHeadersPolicy",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

浏览器的设置方式并不一致 Access-Control-Request-Headers 。 如果:

  • 标头设置为以外的任何内容 "*"
  • AllowAnyHeader 调用:至少包含 AcceptContent-TypeOrigin ,以及要支持的任何自定义标头。

自动预检请求代码

应用 CORS 策略的时间:

  • 通过 app.UseCors 在中调用 Startup.Configure
  • 使用 [EnableCors] 特性。

ASP.NET Core 响应预检 OPTIONS 请求。

使用 基于每个终结点启用 CORS RequireCors 目前 不支持 自动预检请求。

本文档 的"测试 CORS" 部分演示了此行为。

[HttpOptions] 预检请求的属性

使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 CORS 预检请求。 在某些情况下,情况可能并非如此。 例如,将 CORS 与终结点路由 一起使用

以下代码使用 [HttpOptions] 属性为 OPTIONS 请求创建终结点:

[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);
    }

有关测试上述代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。

设置预检过期时间

Access-Control-Max-Age标头指定可以缓存对预检请求的响应的多久。 若要设置此标头,请调用 SetPreflightMaxAge

options.AddPolicy("MySetPreflightExpirationPolicy",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

CORS 的工作原理

本部分介绍在 HTTP 消息级别的 CORS 请求中发生的情况。

  • CORS 不是 安全功能。 CORS 是一种 W3C 标准,允许服务器放宽同源策略。
    • 例如,恶意参与者可能会对站点使用跨站点脚本 (XSS ) ,并针对其启用了 CORS 的站点执行跨站点请求以窃取信息。
  • 通过允许 CORS,API 并不安全。
    • 由客户端和浏览器 () CORS。 服务器执行请求并返回响应,这是返回错误并阻止响应的客户端。 例如,以下任何工具都将显示服务器响应:
  • 这是一种方法,使服务器能够允许浏览器执行跨源 XHR获取 API 请求,否则将被禁止。
    • 没有 CORS 的浏览器不能执行跨域请求。 在 CORS 之前,使用 JSONP 来绕过此限制。 JSONP 不使用 XHR,而是使用 <script> 标记接收响应。 允许跨源加载脚本。

CORS 规范介绍了几个新的 HTTP 标头,它们启用了跨域请求。 如果浏览器支持 CORS,则会自动为跨域请求设置这些标头。 若要启用 CORS,无需自定义 JavaScript 代码。

部署的示例上的 " PUT 测试" 按钮

下面是一个从 " " 测试按钮到的跨源请求的示例 https://cors1.azurewebsites.net/api/valuesOrigin标头:

  • 提供发出请求的站点的域。
  • 是必需的,并且必须与主机不同。

常规标头

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

响应标头

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

请求标头

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

OPTIONS 请求中,服务器设置响应中的 响应标头 Access-Control-Allow-Origin: {allowed origin} 标头。 例如,已部署的 示例 Delete [EnableCors] button OPTIONS 请求包含以下标头:

常规标头

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

响应标头

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

请求标头

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

在之前的 Response 标头中,服务器在响应中设置 Access-Control-Allow-Origin 标头。 https://cors1.azurewebsites.net此标头的值与请求 Origin 中的标头匹配。

如果 AllowAnyOrigin 调用 , Access-Control-Allow-Origin: * 则返回 通配符值。 AllowAnyOrigin 允许任何源。

如果响应不包含 标头 Access-Control-Allow-Origin ,则跨源请求将失败。 具体而言,浏览器禁止该请求。 即使服务器返回成功响应,浏览器也不使响应可用于客户端应用。

显示 OPTIONS 请求

默认情况下,Chrome 和 Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 若要在这些浏览器中显示 OPTIONS 请求,请运行以下操作:

  • chrome://flags/#out-of-blink-corsedge://flags/#out-of-blink-cors
  • 禁用 标志。
  • 重新 启动。

Firefox 默认显示 OPTIONS 请求。

IIS 中的 CORS

部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须先运行 Windows 身份验证。 若要支持此方案,需要为应用安装和配置 IIS CORS 模块。

测试 CORS

示例 下载包含 用于测试 CORS 的代码。 请参阅如何下载。 此示例是添加了 Pages 的 API Razor 项目:

public class StartupTest2
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                builder =>
                {
                    builder.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

警告

WithOrigins("https://localhost:<port>"); 应仅用于测试示例应用程序,类似于 下载示例代码

下面 ValuesController 提供用于测试的终结点:

[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();

}

MyDisplayRouteInfoRick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。

使用以下方法之一测试前面的示例代码:

  • 使用部署的示例应用 https://cors3.azurewebsites.net/ 。 无需下载示例。
  • dotnet run使用的默认 URL 运行示例 https://localhost:5001
  • 从 Visual Studio 运行示例,并将端口设置为44398,查找的 URL https://localhost:44398

使用带有 F12 工具的浏览器:

  • 选择 " " 按钮,然后查看 " 网络 " 选项卡中的标头。

  • 选择 " 放置测试 " 按钮。 请参阅 显示选项请求 ,以获取有关显示选项请求的说明。 PUT 测试 创建两个请求:一个选项预检请求和 PUT 请求。

  • 选择此 GetValues2 [DisableCors] 按钮可触发失败的 CORS 请求。 如文档中所述,响应返回200成功,但不进行 CORS 请求。 选择 " 控制台 " 选项卡以查看 CORS 错误。 根据浏览器,将显示类似于以下内容的错误:

    'https://cors1.azurewebsites.net/api/values/GetValues2'CORS 策略已阻止从原始位置获取的访问权限 'https://cors3.azurewebsites.net' :请求的资源上没有 "访问控制-允许" 标头。 如果非跳转响应可满足需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下提取资源。

可以使用FiddlerPostman等工具来测试启用了CORS 的终结点。 使用工具时,标头指定的请求源 Origin 必须与接收请求的主机不同。 如果请求不是基于标头值 域的,则 Origin

  • CORS 中间件无需处理请求。
  • 不会在响应中返回 CORS 标头。

以下命令使用 curl 发出包含信息的 OPTIONS 请求:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

使用终结点路由和 [HttpOptions] 测试 CORS

使用 基于每个终结点启用 CORS RequireCors 目前 不支持自动预检请求。 请考虑以下代码,该代码使用终结点路由来启用 CORS:

public class StartupEndPointBugTest
{
    readonly string MyPolicy = "_myPolicy";

    // .WithHeaders(HeaderNames.ContentType, "x-custom-header")
    // forces browsers to require a preflight request with GET

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyPolicy,
                builder =>
                {
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com",
                                        "https://cors1.azurewebsites.net",
                                        "https://cors3.azurewebsites.net",
                                        "https://localhost:44398",
                                        "https://localhost:5001")
                           .WithHeaders(HeaderNames.ContentType, "x-custom-header")
                           .WithMethods("PUT", "DELETE", "GET", "OPTIONS");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers().RequireCors(MyPolicy);
            endpoints.MapRazorPages();
        });
    }
}

下面 TodoItems1Controller 提供了用于测试的终结点:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

从已部署示例 的测试页 测试前面的 代码

删除 [EnableCors]GET [EnableCors] 按钮成功,因为终结点具有并响应 [EnableCors] 预检请求。 其他终结点失败。 "GET" 按钮失败,因为 JavaScript发送:

 headers: {
      "Content-Type": "x-custom-header"
 },

下面 TodoItems2Controller 提供了类似的终结点,但包含用于响应 OPTIONS 请求的显式代码:

[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]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

从已部署示例 的测试页测试 上述代码。 在"控制器" 下拉列表中,选择"预检", 然后选择"设置控制器"。 对终结点的所有 CORS TodoItems2Controller 调用都成功。

其他资源

作者:Rick Anderson

本文演示如何在 ASP.NET Core 应用中启用 CORS。

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

跨源资源共享 (CORS) :

  • 是一种 W3C 标准,可让服务器放宽相同的源策略。
  • 是一项安全功能,CORS 放宽 security。 API 不能通过允许 CORS 来更安全。 有关详细信息,请参阅 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:不同的端口

比较来源时,Internet Explorer 不会考虑该端口。

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

CORS 中间件处理跨域请求。 以下代码通过指定源为整个应用启用 CORS:

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

    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(MyAllowSpecificOrigins,
            builder =>
            {
                builder.WithOrigins("http://example.com",
                                    "http://www.contoso.com");
            });
        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseCors(MyAllowSpecificOrigins); 

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

前面的代码:

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
        builder =>
        {
            builder.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

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

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

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
        builder =>
        {
            builder.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
    });

    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

注意:URL 不得 包含尾部 斜杠 / () 。 如果 URL 以 终止 / ,则比较返回 false ,并且不返回标头。

以下代码通过 CORS 中间件将 CORS 策略应用到所有应用终结点:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseCors();

    app.UseHttpsRedirection();
    app.UseMvc();
}

注意: UseCors 必须在 之前调用 UseMvc

请参阅 在页面、控制器 Razor 和操作方法中启用 CORS,以在页面/控制器/操作级别应用 CORS 策略。

有关测试与上述代码类似的代码的说明,请参阅测试CORS。

使用属性启用 CORS

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

使用 [EnableCors] 指定默认策略并 [EnableCors("{Policy String}")] 指定策略。

[EnableCors]属性可应用于:

  • Razor 网页 PageModel
  • 控制器
  • 控制器操作方法

您可以使用属性将不同的策略应用到控制器/页模型/操作 [EnableCors] 。 如果将 [EnableCors] 属性应用于控制器/页面模型/操作方法,并在中间件中启用了 CORS,则会应用 这两种 策略。 建议 不要 合并策略。 使用 [EnableCors] 特性或中间件, 不能同时使用两者。 使用时 [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]        // Default policy.
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        switch (id)
        {
            case 1:
                return "green widget";
            case 2:
                return "red widget";
            default:
                return NotFound();
        }
    }
}

以下代码创建 CORS 默认策略和名为的策略 "AnotherPolicy"

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

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                builder =>
                {
                   
                    builder.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                builder =>
                {
                    builder.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });

        });

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseMvc();
    }
}

禁用 CORS

[ DisableCors ] 属性为控制器/页模型/操作禁用 CORS。

CORS 策略选项

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

AddPolicy 在中调用 Startup.ConfigureServices 。 对于某些选项,最好先阅读 CORS 如何工作 部分。

设置允许的来源

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

备注

指定 AllowAnyOriginAllowCredentials 是不安全的配置,可能会导致跨站点请求伪造。 对于安全应用,如果客户端必须授权自己访问服务器资源,请指定确切的源列表。

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

SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 属性设置为一个函数,该函数允许源在评估是否允许源时匹配配置的 IsOriginAllowed 通配符域。

options.AddPolicy("AllowSubdomain",
    builder =>
    {
        builder.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

设置允许的 HTTP 方法

AllowAnyMethod:

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

设置允许的请求标头

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

options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

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

options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

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

无论 CorsPolicy.Headers 中配置的值如何,CORS 中间件始终允许发送 中的四 Access-Control-Request-Headers 个标头。 此标头列表包括:

  • Accept
  • Accept-Language
  • Content-Language
  • Origin

例如,请考虑配置如下的应用:

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

CORS 中间件使用以下请求标头成功响应到预检请求,因为 Content-Language 始终允许:

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

设置公开的响应标头

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

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

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

CORS 规范将这些标头称为 简单的响应标头。 若要使其他标头可用于应用程序,请调用 WithExposedHeaders

options.AddPolicy("ExposeResponseHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithExposedHeaders("x-custom-header");
    });

跨域请求中的凭据

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

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
  }
});

使用 提取 API

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

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

options.AddPolicy("AllowCredentials",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowCredentials();
    });

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

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

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

CORS 规范还规定,如果标头 (所有源) , "*" 则所有源 Access-Control-Allow-Credentials 的源设置无效。

预检请求

对于某些 CORS 请求,浏览器在发出实际请求之前发送其他请求。 此请求称为 预检请求。 如果满足以下条件,浏览器可以跳过预检请求:

  • 请求方法是 GET、HEAD 或 POST。
  • 应用不会设置除 、 或 外 Accept Accept-Language Content-Language Content-Type 的请求标头 Last-Event-ID
  • 标头 Content-Type (如果已设置)具有以下值之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

针对客户端请求设置的请求标头的规则适用于应用通过调用 对象设置的 setRequestHeader XMLHttpRequest 标头。 CORS 规范调用这些标头 作者请求标头。 此规则不适用于浏览器可以设置的标头,例如 User-Agent Host 、 或 Content-Length

下面是预检请求的示例:

OPTIONS https://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: https://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

预测试请求使用 HTTP OPTIONS 方法。 它包含两个特殊标头:

  • Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
  • Access-Control-Request-Headers:应用在实际请求上设置的请求标头的列表。 如前面所述,这不包括浏览器设置的标题,例如 User-Agent

使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 CORS 预检请求。 有关 预检请求,请参阅 [HttpOptions] 属性

CORS 预检请求可能包含一个 Access-Control-Request-Headers 标头,该标头向服务器指示与实际请求一起发送的标头。

若要允许特定标头,请调用 WithHeaders

options.AddPolicy("AllowHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

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

options.AddPolicy("AllowAllHeaders",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .AllowAnyHeader();
    });

浏览器在设置方式上并不完全一致 Access-Control-Request-Headers 。 如果将标头设置为 (以外的任何内容 "*" 或使用 AllowAnyHeader) ,则应至少包含 AcceptContent-Type 、和 Origin ,以及要支持的任何自定义标头。

下面是 (假定服务器允许) 请求的预检请求的示例响应:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT

响应包含一个 Access-Control-Allow-Methods 标头,其中列出了允许的方法,还可以选择 Access-Control-Allow-Headers 标头,其中列出了允许的标头。 如果预检请求成功,则浏览器发送实际请求。

如果预检请求被拒绝,应用将返回 200 OK 响应,但不会向后发送 CORS 标头。 因此,浏览器不会尝试跨域请求。

设置预检过期时间

Access-Control-Max-Age标头指定可缓存对预检请求的响应的时间长度。 若要设置此标头,请调用 SetPreflightMaxAge

options.AddPolicy("SetPreflightExpiration",
    builder =>
    {
        builder.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

CORS 如何工作

本部分介绍 HTTP 消息级别的 CORS 请求中发生的情况。

  • CORS 是一种安全功能。 CORS 是一种 W3C 标准,可让服务器放宽相同的源策略。
    • 例如,恶意执行组件可能会对站点使用 阻止跨站点脚本 (XSS) ,并向启用了 CORS 的站点执行跨站点请求来窃取信息。
  • API 不能通过允许 CORS 来更安全。
    • 它由客户端 (浏览器) 来强制执行 CORS。 服务器执行请求并返回响应,这是返回错误并阻止响应的客户端。 例如,以下任何工具都会显示服务器响应:
  • 这是服务器允许浏览器执行跨源 XHR提取 API 请求的一种方法,否则将禁止该请求。
    • 没有 CORS (浏览器) 无法执行跨源请求。 在 CORS 之前 ,JSONP 用于规避此限制。 JSONP 不使用 XHR,它使用 <script> 标记来接收响应。 允许跨源加载脚本。

CORS 规范引入了几个支持跨源请求的新 HTTP 标头。 如果浏览器支持 CORS,则会自动为跨源请求设置这些标头。 启用 CORS 不需要自定义 JavaScript 代码。

下面是跨源请求的示例。 Origin标头提供进行请求的站点的域。 Origin标头是必需的,并且必须与主机不同。

GET https://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: https://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: https://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

如果服务器允许请求,它会在 Access-Control-Allow-Origin 响应中设置 标头。 此标头的值要么与请求中的标头匹配,要么为通配符 Origin"*" ,这意味着允许任何源:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12

Test message

如果响应不包含 标头 Access-Control-Allow-Origin ,则跨源请求将失败。 具体而言,浏览器禁止该请求。 即使服务器返回成功响应,浏览器也不使响应可用于客户端应用。

测试 CORS

测试 CORS:

  1. 创建 API 项目。 或者,您也可以 下载该示例
  2. 使用本文档中的方法之一启用 CORS。 例如:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    // Shows UseCors with CorsPolicyBuilder.
    app.UseCors(builder =>
    {
        builder.WithOrigins("http://example.com",
                            "http://www.contoso.com",
                            "https://localhost:44375",
                            "https://localhost:5001");
    });

    app.UseHttpsRedirection();
    app.UseMvc();
}

警告

WithOrigins("https://localhost:<port>"); 应仅用于测试示例应用程序,类似于 下载示例代码

  1. (Razor 页面或 MVC) 创建 web 应用项目。 该示例使用 Razor 页面。 可以在与 API 项目相同的解决方案中创建 web 应用。
  2. 将以下突出显示的代码添加到 索引 cshtml 文件中:
@page
@model IndexModel
@{
    ViewData["Title"] = "Home page";
}

<div class="text-center">
    <h1 class="display-4">CORS Test</h1>
</div>

<div>
    <input type="button" value="Test" 
           onclick="requestVal('https://<web app>.azurewebsites.net/api/values')" />
    <span id='result'></span>
</div>

<script>
    function requestVal(uri) {
        const resultSpan = document.getElementById('result');

        fetch(uri)
            .then(response => response.json())
            .then(data => resultSpan.innerText = data)
            .catch(error => resultSpan.innerText = 'See F12 Console for error');
    }
</script>
  1. 在上面的代码中,将替换 url: 'https://<web app>.azurewebsites.net/api/values/1', 为已部署应用的 URL。

  2. 部署 API 项目。 例如, 部署到 Azure

  3. Razor从桌面运行页面或 MVC 应用,并单击 "测试" 按钮。 使用 F12 工具查看错误消息。

  4. 从中删除 localhost 源 WithOrigins 并部署应用。 或者,使用其他端口运行客户端应用。 例如,在 Visual Studio 中运行。

  5. 与客户端应用程序进行测试。 CORS 故障返回一个错误,但错误消息不能用于 JavaScript。 使用 F12 工具中的 "控制台" 选项卡查看错误。 根据浏览器的不同,在 F12 (控制台中) 如下所示:

    • 使用Microsoft Edge:

      SEC7120:[CORS] 在 跨源资源的 https://localhost:44375 https://localhost:44375 Access-Control-Allow-Origin 响应标头中找不到源 https://webapi.azurewebsites.net/api/values/1

    • 使用 Chrome:

      CORS 策略阻止从源访问 XMLHttpRequest:请求的资源上不存在 https://webapi.azurewebsites.net/api/values/1 https://localhost:44375 "Access-Control-Allow-Origin"标头。

可以使用 FiddlerPostman等工具测试启用了 CORS 的终结点。 使用工具时,标头指定的请求的来源必须与接收请求 Origin 的主机不同。 如果请求不是基于 标头的值 的跨源 Origin 请求:

  • CORS 中间件无需处理请求。
  • 不会在响应中返回 CORS 标头。

IIS 中的 CORS

部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须先运行 Windows 身份验证。 若要支持此方案,需要为应用安装和配置 IIS CORS 模块。

其他资源