在 ASP.NET Core 中启用跨源请求 (CORS)
作者: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 中间件必须配置为在对 UseRouting
和 UseEndpoints
的调用之间执行。
有关测试与前面代码类似的代码的说明,请参阅测试 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 和 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 不支持自动预检请求。有关详细信息,请参阅此GitHub使用终结点路由和 [HttpOptions] 测试 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。
有关测试与前面代码类似的代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。 [EnableCors]
属性为所选终结点(而不是所有终结点)启用 CORS:
[EnableCors]
指定默认策略。[EnableCors("{Policy String}")]
指定命名策略。
[EnableCors]
属性可应用于:
- Razor Page
PageModel
- 控制器
- 控制器操作方法
可将不同的策略应用于具有
以下代码对每种方法应用不同的策略: 以下代码创建两个 CORS 策略: 为了最精细地控制 CORS 请求的限制: 下一部分中的代码符合前面的列表。 有关测试与前面代码类似的代码的说明,请参阅测试 CORS。 [DisableCors] 属性不会禁用终结点路由已启用的 CORS。 以下代码定义 CORS 策略 以下代码为 前面的代码: 有关测试前面代码的说明,请参阅测试 CORS。 本部分介绍可以在 CORS 策略中设置的各种选项: AddPolicy 在 AllowAnyOrigin:允许具有任何方案( 注意 指定 SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 IsOriginAllowed 属性设置为一个函数,当计算是否允许源时,此函数允许源匹配已配置的通配符域。 要允许在称为作者请求头的 CORS 请求中发送特定头,请调用 WithHeaders 并指定允许的头: 要允许所有 作者请求头,请调用 AllowAnyHeader: 仅当在 例如,考虑按如下方式配置的应用: CORS 中间件拒绝具有以下请求头的预检请求,因为 应用返回 200 OK 响应,但不发回 CORS 头。 因此,浏览器不会尝试跨源请求。 默认情况下,浏览器不会向应用公开所有响应头。 有关详细信息,请参阅 W3C 跨源资源共享(术语):简单响应头。 默认情况下可用的响应头包括: CORS 规范将这些头称为简单响应头。 要使其他头可用于应用,请调用 WithExposedHeaders: 凭据需要在 CORS 请求中进行特殊处理。 默认情况下,浏览器不会使用跨源域请求发送凭据。 凭据包括 cookie 和 HTTP 身份验证方案。 要使用跨源请求发送凭据,客户端必须将 直接使用 使用 jQuery: 使用 Fetch API: 服务器必须允许凭据。 要允许跨源凭据,请调用 AllowCredentials: HTTP 响应包含一个 如果浏览器发送凭据,但响应不包含有效的 允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。 CORS 规范还指出,如果存在 对于某些 CORS 请求,浏览器会在发出实际请求之前发送额外的 OPTIONS 请求。 此请求称为预检请求。 如果满足以下所有条件,浏览器可以跳过预检请求: 为客户端请求设置的请求头规则适用于应用通过在 以下是与本文档测试 CORS 部分中的 [Put test] 按钮发出的预检请求类似的示例响应。 预检请求使用 HTTP OPTIONS 方法。 它可能包含以下头: 如果预检请求被拒绝,应用将返回 使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器: 要允许特定的头,请调用 WithHeaders: 要允许所有 作者请求头,请调用 AllowAnyHeader: 浏览器设置 通过以下方式应用 CORS 策略时: ASP.NET Core 响应预检 OPTIONS 请求。 使用 本文档的测试 CORS 部分演示了这种行为。 当使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 CORS 预检请求。 在某些情况下,可能并非如此。 例如,通过终结点路由使用 CORS。 以下代码使用 [HttpOptions] 属性为 OPTIONS 请求创建终结点: 有关测试前面代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。 本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。 CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,则会自动为跨源请求设置这些头。 启用 CORS 不需要自定义 JavaScript 代码。 已部署示例上的 PUT test 按钮 以下是从 Values 测试按钮到 通用头 响应标头 请求标头 在 通用头 响应标头 请求标头 在前面的响应头中,服务器在响应中设置了 Access-Control-Allow-Origin 头。 此头的 如果调用 AllowAnyOrigin,则返回通配符值 如果响应不包含 UseHttpsRedirection 使用 HTTP(但重定向到 HTTPS)对终结点进行的请求失败,并返回 API 项目可以拒绝 HTTP 请求,而不是使用 默认情况下,Chrome 和 Microsoft Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 要在这些浏览器中显示 OPTIONS 请求,请执行以下操作: Firefox 默认显示 OPTIONS 请求。 部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。 示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个 API 项目,其中添加了 Razor Pages: 警告 以下 MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。 使用以下方法之一测试上述示例代码: 使用带有 F12 工具的浏览器: 选择“值”按钮并在“网络”选项卡中查看头。 选择“PUT test”按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT test 会创建两个请求,一个 OPTIONS 预检请求和一个 PUT 请求。 选择此 从源 启用了 CORS 的终结点可以使用 curl、Fiddler 或 Postman 等工具进行测试。 使用工具时, 以下命令使用 使用 以下 Delete [EnableCors] 和 GET [EnableCors] 按钮成功,因为终结点具有 以下 从已部署示例的测试页测试前面的代码。 在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 [EnableCors]
属性的控制器、页面模型或操作方法。 [EnableCors]
将属性应用于控制器、页面模型或操作方法,并在中间件中启用 CORS 时,将应用这两个策略。 建议不要合并策略。 [EnableCors]
使用同一应用中的attribute 或中间件,而不是同时使用中间件。[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(),
};
}
}
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();
[EnableCors("MyPolicy")]
与命名策略一起使用。禁用 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.MapControllers();
app.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();
}
"MyPolicy"
CORS 策略。GetValues2
方法禁用 CORS。CORS 策略选项
Program.cs
中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。设置允许的源
http
或 https
)的所有源的 CORS 请求。 AllowAnyOrigin
不安全,因为 任何网站 都可以向应用发出跨域请求。AllowAnyOrigin
和 AllowCredentials
是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,CORS 服务会返回无效的 CORS 响应。AllowAnyOrigin
影响预检请求和 Access-Control-Allow-Origin
头。 有关详细信息,请参阅预检请求部分。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 方法
Access-Control-Allow-Methods
头。 有关详细信息,请参阅预检请求部分。设置允许的请求头
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();
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));
Content-Language
(HeaderNames.ContentLanguage) 未列在 WithHeaders
中:Access-Control-Request-Headers: Cache-Control, Content-Language
设置公开的响应头
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
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();
跨源请求中的凭据
XMLHttpRequest.withCredentials
设置为 true
。XMLHttpRequest
:var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;
$.ajax({
type: 'get',
url: 'https://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
});
fetch('https://www.example.com/api/test', {
credentials: 'include'
});
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();
Access-Control-Allow-Credentials
头,它告诉浏览器服务器允许跨源请求的凭据。Access-Control-Allow-Credentials
头,则浏览器不会向应用公开响应,而且跨源请求会失败。Access-Control-Allow-Credentials
头,则将源设置为 "*"
(所有源)是无效的。预检请求
Accept
、Accept-Language
、Content-Language
、Content-Type
或 Last-Event-ID
以外的请求头。Content-Type
头(如果已设置)具有以下值之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
XMLHttpRequest
对象上调用 setRequestHeader
设置的头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent
、Host
或 Content-Length
。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
User-Agent
。200 OK
响应,但不会设置 CORS 头。 因此,浏览器不会尝试跨源请求。 有关被拒绝的预检请求的示例,请参阅本文档的测试 CORS 部分。
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5
上的远程资源。 (原因:CORS 请求不成功)。 了解详细信息using Microsoft.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("MyAllowHeadersPolicy",
policy =>
{
policy.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
});
builder.Services.AddControllers();
var app = builder.Build();
using Microsoft.Net.Http.Headers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("MyAllowAllHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.AllowAnyHeader();
});
});
builder.Services.AddControllers();
var app = builder.Build();
Access-Control-Request-Headers
的方式不一致。 如果:
"*"
以外的任何内容Accept
、Content-Type
和 Origin
,以及你想要支持的任何自定义头。自动预检请求代码
Program.cs
中调用 app.UseCors
在全局范围内应用。[EnableCors]
属性。RequireCors
每个终结点启用 CORS 目前 不支持 自动预检请求。用于预检请求的 [HttpOptions] 属性
[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);
}
设置预检过期时间
Access-Control-Max-Age
头指定对预检请求的响应可以缓存多长时间。 要设置此头,请调用 SetPreflightMaxAge:var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("MySetPreflightExpirationPolicy",
policy =>
{
policy.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
});
builder.Services.AddControllers();
var app = builder.Build();
CORS 的工作原理
<script>
标记来接收响应。 允许跨源加载脚本。https://cors1.azurewebsites.net/api/values
的跨源请求示例。 Origin
头:
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] 按钮 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
https://cors1.azurewebsites.net
值与请求中的 Origin
头一致。Access-Control-Allow-Origin: *
。 AllowAnyOrigin
允许任何源。Access-Control-Allow-Origin
头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。HTTP 重定向到 HTTPS 会导致 CORS 预检请求出现 ERR_INVALID_REDIRECT
ERR_INVALID_REDIRECT on the CORS preflight request
。UseHttpsRedirection
将请求重定向到 HTTPS。显示 OPTIONS 请求
chrome://flags/#out-of-blink-cors
或 edge://flags/#out-of-blink-cors
IIS 中的 CORS
测试 CORS
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.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");
});
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.MapRazorPages();
app.Run();
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();
}
https://localhost:5001
的默认 URL 运行带有 dotnet run
的示例。https://localhost:44398
的 URL 将端口设置为 44398。
GetValues2 [DisableCors]
按钮可触发失败的 CORS 请求。 如文档中所述,响应返回 200 成功,但没有发出 CORS 请求。 选择“控制台”选项卡可查看 CORS 错误。 根据浏览器的不同,将显示类似于以下内容的错误:'https://cors3.azurewebsites.net'
中的 'https://cors1.azurewebsites.net/api/values/GetValues2'
提取的访问已被 CORS 策略阻止:请求的资源上不存在 Access-Control-Allow-Origin 头。 如果非跳转响应可满足需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下提取资源。Origin
头指定的请求源必须与接收请求的主机不同。 如果根据 Origin
头的值,请求未跨源,则:
curl
发出包含信息的 OPTIONS 请求:curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i
使用终结点路由和 [HttpOptions] 测试 CORS
RequireCors
每个终结点启用 CORS 目前不支持自动预检请求。 请考虑以下代码,该代码使用终结点路由来启用 CORS:var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.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");
});
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.MapRazorPages();
app.Run();
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 按钮失败,因为 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);
}
TodoItems2Controller
终结点的所有 CORS 调用都成功。其他资源
作者: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 策略应用于具有指定源的所有应用终结点:
public class Startup
{
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.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();
});
}
}
前面的代码:
- 将策略名称设置为
_myAllowSpecificOrigins
。 策略名称是任意的。 - 调用 UseCors 扩展方法并指定
_myAllowSpecificOrigins
CORS 策略。UseCors
添加 CORS 中间件。 对UseCors
的调用必须放在UseRouting
之后,但在UseAuthorization
之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式调用 AddCors。 lambda 采用 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins
。 - 为所有控制器终结点启用
_myAllowSpecificOrigins
CORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseResponseCaching 之前调用 UseCors。
对于终结点路由,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,
policy =>
{
policy.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,
policy =>
{
policy.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(
policy =>
{
policy.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
使用RequireCors
每个终结点启用 CORS 不支持自动预检请求。有关详细信息,请参阅此GitHub使用终结点路由和 [HttpOptions] 测试 CORS 的问题和测试 CORS。
对于终结点路由,可以使用 RequireCors 扩展方法集基于每个终结点启用 CORS:
public class Startup
{
readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.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
和控制器终结点允许使用指定策略的跨源请求。- 和 Pages 终结点不允许跨域请求,因为未指定默认策略。Razor
/echo2
[DisableCors] 属性不会禁用终结点路由已使用 RequireCors
启用的 CORS。
有关测试与前面代码类似的代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。 [EnableCors]
属性为所选终结点(而不是所有终结点)启用 CORS:
[EnableCors]
指定默认策略。[EnableCors("{Policy String}")]
指定命名策略。
[EnableCors]
属性可应用于:
- Razor Page
PageModel
- 控制器
- 控制器操作方法
可将不同的策略应用于具有
以下代码对每种方法应用不同的策略: 以下代码创建两个 CORS 策略: 为了最精细地控制 CORS 请求的限制: 下一部分中的代码符合前面的列表。 有关测试与前面代码类似的代码的说明,请参阅测试 CORS。 [DisableCors] 属性不会禁用终结点路由已启用的 CORS。 以下代码定义 CORS 策略 以下代码为 前面的代码: 有关测试前面代码的说明,请参阅测试 CORS。 本部分介绍可以在 CORS 策略中设置的各种选项: AddPolicy 在 AllowAnyOrigin:允许具有任何方案( 注意 指定 SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 IsOriginAllowed 属性设置为一个函数,当计算是否允许源时,此函数允许源匹配已配置的通配符域。 要允许在称为作者请求头的 CORS 请求中发送特定头,请调用 WithHeaders 并指定允许的头: 要允许所有 作者请求头,请调用 AllowAnyHeader: 仅当在 例如,考虑按如下方式配置的应用: CORS 中间件拒绝具有以下请求头的预检请求,因为 应用返回 200 OK 响应,但不发回 CORS 头。 因此,浏览器不会尝试跨源请求。 默认情况下,浏览器不会向应用公开所有响应头。 有关详细信息,请参阅 W3C 跨源资源共享(术语):简单响应头。 默认情况下可用的响应头包括: CORS 规范将这些头称为简单响应头。 要使其他头可用于应用,请调用 WithExposedHeaders: 凭据需要在 CORS 请求中进行特殊处理。 默认情况下,浏览器不会使用跨源域请求发送凭据。 凭据包括 cookie 和 HTTP 身份验证方案。 要使用跨源请求发送凭据,客户端必须将 直接使用 使用 jQuery: 使用 Fetch API: 服务器必须允许凭据。 要允许跨源凭据,请调用 AllowCredentials: HTTP 响应包含一个 如果浏览器发送凭据,但响应不包含有效的 允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。 CORS 规范还指出,如果存在 对于某些 CORS 请求,浏览器会在发出实际请求之前发送额外的 OPTIONS 请求。 此请求称为预检请求。 如果满足以下所有条件,浏览器可以跳过预检请求: 为客户端请求设置的请求头规则适用于应用通过在 以下是与本文档测试 CORS 部分中的 [Put test] 按钮发出的预检请求类似的示例响应。 预检请求使用 HTTP OPTIONS 方法。 它可能包含以下头: 如果预检请求被拒绝,应用将返回 使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器: 要允许特定的头,请调用 WithHeaders: 要允许所有 作者请求头,请调用 AllowAnyHeader: 浏览器设置 通过以下方式应用 CORS 策略时: ASP.NET Core 响应预检 OPTIONS 请求。 使用 本文档的测试 CORS 部分演示了这种行为。 当使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 CORS 预检请求。 在某些情况下,可能并非如此。 例如,通过终结点路由使用 CORS。 以下代码使用 [HttpOptions] 属性为 OPTIONS 请求创建终结点: 有关测试前面代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。 本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。 CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,则会自动为跨源请求设置这些头。 启用 CORS 不需要自定义 JavaScript 代码。 已部署示例上的 PUT test 按钮 以下是从 Values 测试按钮到 通用头 响应标头 请求标头 在请求中 通用头 响应标头 请求标头 在前面的响应头中,服务器在响应中设置了 Access-Control-Allow-Origin 头。 此头的 如果调用 AllowAnyOrigin,则返回通配符值 如果响应不包含 默认情况下,Chrome 和 Microsoft Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 要在这些浏览器中显示 OPTIONS 请求,请执行以下操作: Firefox 默认显示 OPTIONS 请求。 部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。 示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个 API 项目,其中添加了 Razor Pages: 警告 以下 MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。 使用以下方法之一测试上述示例代码: 使用带有 F12 工具的浏览器: 选择“值”按钮并在“网络”选项卡中查看头。 选择“PUT test”按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT test 会创建两个请求,一个 OPTIONS 预检请求和一个 PUT 请求。 选择此 从源 启用了 CORS 的终结点可以使用 curl、Fiddler 或 Postman 等工具进行测试。 使用工具时, 以下命令使用 使用 以下 Delete [EnableCors] 和 GET [EnableCors] 按钮成功,因为终结点具有 以下 从已部署示例的测试页测试前面的代码。 在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 [EnableCors]
属性的控制器、页面模型或操作方法。 [EnableCors]
将属性应用于控制器、页面模型或操作方法,并在中间件中启用 CORS 时,将应用这两个策略。 建议不要合并策略。 [EnableCors]
使用同一应用中的attribute 或中间件,而不是同时使用中间件。[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(),
};
}
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
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();
});
});
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();
});
}
}
[EnableCors("MyPolicy")]
与命名策略一起使用。禁用 CORS
"MyPolicy"
:public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.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();
});
}
}
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();
}
"MyPolicy"
CORS 策略。GetValues2
方法禁用 CORS。CORS 策略选项
Startup.ConfigureServices
中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。设置允许的源
http
或 https
)的所有源的 CORS 请求。 AllowAnyOrigin
不安全,因为 任何网站 都可以向应用发出跨域请求。AllowAnyOrigin
和 AllowCredentials
是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,CORS 服务会返回无效的 CORS 响应。AllowAnyOrigin
影响预检请求和 Access-Control-Allow-Origin
头。 有关详细信息,请参阅预检请求部分。options.AddPolicy("MyAllowSubdomainPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
设置允许的 HTTP 方法
Access-Control-Allow-Methods
头。 有关详细信息,请参阅预检请求部分。设置允许的请求头
options.AddPolicy("MyAllowHeadersPolicy",
policy =>
{
// requires using Microsoft.Net.Http.Headers;
policy.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
options.AddPolicy("MyAllowAllHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.AllowAnyHeader();
});
AllowAnyHeader
影响预检请求和 Access-Control-Request-Headers 头。 有关详细信息,请参阅预检请求部分。Access-Control-Request-Headers
中发送的头与 WithHeaders
中所述的头完全匹配时,才能与 WithHeaders
指定的特定头匹配 CORS 中间件策略。app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
Content-Language
(HeaderNames.ContentLanguage) 未列在 WithHeaders
中:Access-Control-Request-Headers: Cache-Control, Content-Language
设置公开的响应头
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
options.AddPolicy("MyExposeResponseHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.WithExposedHeaders("x-custom-header");
});
跨源请求中的凭据
XMLHttpRequest.withCredentials
设置为 true
。XMLHttpRequest
:var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;
$.ajax({
type: 'get',
url: 'https://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
});
fetch('https://www.example.com/api/test', {
credentials: 'include'
});
options.AddPolicy("MyMyAllowCredentialsPolicy",
policy =>
{
policy.WithOrigins("http://example.com")
.AllowCredentials();
});
Access-Control-Allow-Credentials
头,它告诉浏览器服务器允许跨源请求的凭据。Access-Control-Allow-Credentials
头,则浏览器不会向应用公开响应,而且跨源请求会失败。Access-Control-Allow-Credentials
头,则将源设置为 "*"
(所有源)是无效的。预检请求
Accept
、Accept-Language
、Content-Language
、Content-Type
或 Last-Event-ID
以外的请求头。Content-Type
头(如果已设置)具有以下值之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
XMLHttpRequest
对象上调用 setRequestHeader
设置的头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent
、Host
或 Content-Length
。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
User-Agent
。200 OK
响应,但不会设置 CORS 头。 因此,浏览器不会尝试跨源请求。 有关被拒绝的预检请求的示例,请参阅本文档的测试 CORS 部分。
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5
上的远程资源。 (原因:CORS 请求不成功)。 了解详细信息options.AddPolicy("MyAllowHeadersPolicy",
policy =>
{
// requires using Microsoft.Net.Http.Headers;
policy.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
options.AddPolicy("MyAllowAllHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.AllowAnyHeader();
});
Access-Control-Request-Headers
的方式不一致。 如果:
"*"
以外的任何内容Accept
、Content-Type
和 Origin
,以及你想要支持的任何自定义头。自动预检请求代码
Startup.Configure
中调用 app.UseCors
在全局范围内应用。[EnableCors]
属性。RequireCors
每个终结点启用 CORS 目前 不支持 自动预检请求。用于预检请求的 [HttpOptions] 属性
[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);
}
设置预检过期时间
Access-Control-Max-Age
头指定对预检请求的响应可以缓存多长时间。 要设置此头,请调用 SetPreflightMaxAge:options.AddPolicy("MySetPreflightExpirationPolicy",
policy =>
{
policy.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
CORS 的工作原理
<script>
标记来接收响应。 允许跨源加载脚本。https://cors1.azurewebsites.net/api/values
的跨源请求示例。 Origin
头:
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] 按钮 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
https://cors1.azurewebsites.net
值与请求中的 Origin
头一致。Access-Control-Allow-Origin: *
。 AllowAnyOrigin
允许任何源。Access-Control-Allow-Origin
头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。显示 OPTIONS 请求
chrome://flags/#out-of-blink-cors
或 edge://flags/#out-of-blink-cors
IIS 中的 CORS
测试 CORS
public class StartupTest2
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.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();
}
https://localhost:5001
的默认 URL 运行带有 dotnet run
的示例。https://localhost:44398
的 URL 将端口设置为 44398。
GetValues2 [DisableCors]
按钮可触发失败的 CORS 请求。 如文档中所述,响应返回 200 成功,但没有发出 CORS 请求。 选择“控制台”选项卡可查看 CORS 错误。 根据浏览器的不同,将显示类似于以下内容的错误:'https://cors3.azurewebsites.net'
中的 'https://cors1.azurewebsites.net/api/values/GetValues2'
提取的访问已被 CORS 策略阻止:请求的资源上不存在 Access-Control-Allow-Origin 头。 如果非跳转响应可满足需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下提取资源。Origin
头指定的请求源必须与接收请求的主机不同。 如果根据 Origin
头的值,请求未跨源,则:
curl
发出包含信息的 OPTIONS 请求:curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i
使用终结点路由和 [HttpOptions] 测试 CORS
RequireCors
每个终结点启用 CORS 目前不支持自动预检请求。 请考虑以下代码,该代码使用终结点路由来启用 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,
policy =>
{
policy.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 按钮失败,因为 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);
}
TodoItems2Controller
终结点的所有 CORS 调用都成功。其他资源