Note
此版本不是本文的最新版本。 有关当前版本,请参阅 本文的 .NET 10 版本。
Warning
此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 有关当前版本,请参阅 本文的 .NET 10 版本。
作者: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.htmlhttps://example.com/bar.html
这些 URL 的源与前两个 URL 不同:
-
https://example.net:不同域 -
https://contoso.example.com/foo.html:不同的子域 -
http://example.com/foo.html:不同的方案 -
https://example.com:9000/foo.html:不同的端口
启用 CORS
有三种方法可以启用 CORS:
- 在使用命名策略或默认策略的中间件中。
- 使用终结点路由。
- 使用 [EnableCors] 属性。
将 [EnableCors] 属性与命名策略一起使用可在限制支持 CORS 的终结点方面提供最佳控制。
Warning
UseCors 必须按正确的顺序调用。 有关详细信息,请参阅中间件顺序。 例如,使用 UseCors 时,必须在 UseResponseCaching 之前调用 UseResponseCaching。
以下各部分详细介绍了每种方法。
具有命名策略和中间件的 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 扩展方法并指定
_myAllowSpecificOriginsCORS 策略。UseCors添加 CORS 中间件。 对UseCors的调用必须放在UseRouting之后,但在UseAuthorization之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式 调用 AddCors。 Lambda 表达式接受一个 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins。 - 为所有控制器终结点启用
_myAllowSpecificOriginsCORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseCors 之前调用 UseResponseCaching。
使用终结点路由时,CORS 中间件 必须 配置为在调用 UseRouting 和 UseEndpoints 之间执行。
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 跨站点检索静态文件的应用必须在 UseCors 之前调用 UseStaticFiles。
具有默认策略和中间件的 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和控制器终结点允许使用指定策略的跨源请求。 -
/echo2和 Razor Pages 终结点不允许跨源请求,因为未指定默认策略。
[DisableCors] 属性不会禁用由终结点路由结合 RequireCors 启用的 CORS。
有关测试与前面代码类似的代码的说明,请参阅使用 [EnableCors] 属性和 RequireCors 方法测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。
[EnableCors] 属性为所选终结点(而不是所有终结点)启用 CORS:
-
[EnableCors]指定默认策略。 -
[EnableCors("{Policy String}")]指定命名策略。
[EnableCors] 属性可应用于:
-
Razor 页
PageModel - Controller
- 控制器操作方法
可将不同的策略应用于具有 [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
[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 策略中设置的各种选项:
AddPolicy 在 Program.cs 中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。
设置允许的来源
AllowAnyOrigin:允许来自所有源且使用任意方案(http 或 https)的 CORS 请求。
AllowAnyOrigin 不安全,因为任何网站都可以向应用发出跨源请求。
Note
指定 AllowAnyOrigin 和 AllowCredentials 是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,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();
在前面的代码中, SetIsOriginAllowedToAllowWildcardSubdomains 使用通配符源 "https://*.example.com"调用。 此配置允许来自example.com的任何子域(例如https://subdomain.example.com或https://api.example.com)的 CORS 请求。 必须在 * 源中包含通配符才能启用通配符子域匹配。
设置允许的 HTTP 方法
- 允许任何 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 中所述的标头完全匹配时,CORS 中间件策略才可能匹配由 WithHeaders 指定的特定标头。
例如,考虑按如下方式配置的应用:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中间件拒绝包含以下请求标头的预检请求,因为 Content-Language (HeaderNames.ContentLanguage) 未列在 WithHeaders 中:
Access-Control-Request-Headers: Cache-Control, Content-Language
应用返回 204 No Content 响应,但未返回 CORS 标头。 因此,浏览器不会尝试跨源请求。
设置公开的响应头
默认情况下,浏览器不会向应用公开所有响应头。 有关详细信息,请参阅 W3C 跨源资源共享(术语):简单响应头。
默认情况下可用的响应头包括:
Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
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 头,则浏览器不会向应用公开响应,而且跨源请求会失败。
允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。
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-urlencodedmultipart/form-datatext/plain
为客户端请求设置的请求标头规则适用于应用通过在 XMLHttpRequest 对象上调用 setRequestHeader 来设置的这些标头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent、Host 或 Content-Length。
Note
本文包含通过将示例代码部署到两个 Azure 网站(https://cors3.azurewebsites.net 和 https://cors.azurewebsites.net)创建的 URL。
以下是一个响应示例,与本文档Test 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 方法。 可包括以下标头:
- Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
-
Access-Control-Request-Headers:应用在实际请求上设置的请求头的列表。 如前文所述,这不包含浏览器设置的标头,如
User-Agent。
如果预检请求被拒绝,应用将返回一个 204 No Content 响应,但不会设置 CORS 标头。 因此,浏览器不会尝试跨源请求。 如需查看被拒绝的预检请求示例,请参阅本文档中的测试 CORS部分。
使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器:
- Firefox:跨源请求被阻止:同源策略不允许读取
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5上的远程资源。 (原因:CORS 请求不成功)。 了解更多信息 - 基于 Chromium:从源“https://cors3.azurewebsites.net”访问“https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5”的请求已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:请求的资源中不存在“Access-Control-Allow-Origin”标头。 如果不透明响应能够满足你的需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下获取该资源。
若要允许特定标头,请调用 WithHeaders:
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();
要允许所有作者请求标头,请调用 AllowAnyHeader:
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 的方式不一致。 如果以下任一项:
- 标头被设置为除
"*"之外的任意值 - 称为 AllowAnyHeader:至少包括
Accept、Content-Type和Origin,以及任何你想支持的自定义标头。
自动预检请求代码
当以以下任一方式应用 CORS 策略时:
- 通过在
Program.cs中调用app.UseCors,在全局范围内生效。 - 使用
[EnableCors]属性。
ASP.NET Core 响应预检 OPTIONS 请求。
本文档的测试 CORS 部分演示了这种行为。
用于预检请求的 [HttpOptions] 属性
当使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 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);
}
有关测试上述代码的说明,请参阅使用 [EnableCors] 属性和 RequireCors 方法测试 CORS。
设置预检过期时间
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
CORS 的工作原理
本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。
- CORS 不是一项安全功能。 CORS 是一种 W3C 标准,允许服务器放宽同源策略。
- 例如,恶意行为者可能对你的网站使用跨站脚本 (XSS),并向其启用了 CORS 的网站执行跨站请求以窃取信息。
- 允许 CORS 并不会使 API 更安全。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- Fiddler
- .NET HttpClient
- 通过在地址栏中输入 URL 的 Web 浏览器。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- 通过这种方式,服务器允许浏览器执行跨源 XHR 或 Fetch API 请求,否则会被禁止。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
<script>标记来接收响应。 允许跨源加载脚本。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,就会自动为跨源请求设置这些标头。 启用 CORS 不需要自定义 JavaScript 代码。
以下是从 Values 测试按钮到 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
在前面的 响应标头 中,服务器在响应中设置了 Access-Control-Allow-Origin 标头。 此头的 https://cors1.azurewebsites.net 值与请求中的 Origin 头一致。
如果调用 AllowAnyOrigin,则返回通配符值 Access-Control-Allow-Origin: *。
AllowAnyOrigin 允许任意来源。
如果响应不包含 Access-Control-Allow-Origin 头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。
从 HTTP 重定向到 HTTPS 会导致 CORS 预检请求出现 ERR_INVALID_REDIRECT 错误
使用 HTTP 向终结点发出的请求在被 UseHttpsRedirection 重定向到 HTTPS 时会失败,并出现 ERR_INVALID_REDIRECT on the CORS preflight request。
API 项目可以拒绝 HTTP 请求,而不是使用 UseHttpsRedirection 将请求重定向到 HTTPS。
IIS 中的 CORS
部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。
测试 CORS
示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个已添加 Razor 页面的 API 项目:
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();
Warning
WithOrigins("https://localhost:<port>"); 应仅用于测试类似于下载示例代码的示例代码。
Note
如果您在 launchSettings.json 中使用 Visual Studio 或在 VS Code 中配置 C# 调试设置,并使用 IIS Express 进行本地调试,请确保已为 "anonymousAuthentication": true 配置 IIS Express。 当 "anonymousAuthentication" 为 false 时,ASP.NET Core Web 环境主机将不会收到任何预检请求。 特别是,如果使用的是 NTLM 身份验证 ("windowsAuthentication": true),NTLM 质询响应过程的第一步是向 Web 浏览器发送一个 401 质询,这可能会使验证预检路由是否配置正确变得困难。
以下 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();
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
使用以下方法之一测试上述示例代码:
- 使用
dotnet run运行该示例,并使用https://localhost:5001的默认 URL。 - 从 Visual Studio 运行示例,并针对
https://localhost:44398的 URL 将端口设置为 44398。
使用带有 F12 工具的浏览器:
单击值按钮,并在网络选项卡中查看标头。
选择PUT test按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT 测试会创建两个请求:一个 OPTIONS 预检请求和一个 PUT 请求。
选择此
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 的情况下获取该资源。
启用了 CORS 的终结点可以使用 curl 或 Fiddler 等工具进行测试。 使用工具时,Origin 头指定的请求源必须与接收请求的主机不同。 如果根据 Origin 标头的值,请求不属于跨源,则:
- 无需 CORS 中间件来处理请求。
- 响应中未返回 CORS 标头。
以下命令使用 curl 发出包含信息的 OPTIONS 请求:
curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i
使用 [EnableCors] 属性和 RequireCors 方法测试 CORS
请考虑以下代码,该代码使用终结点路由,通过 RequireCors 按终结点启用 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.UseEndpoints(endpoints =>
{
endpoints.MapGet("/echo",
context => context.Response.WriteAsync("echo"))
.RequireCors("MyPolicy");
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.Run();
请注意,只有 /echo 终结点使用 RequireCors 来允许使用指定策略的跨域请求。 下面的控制器使用 [EnableCors] 属性来启用 CORS。
以下 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("MyPolicy")]
[HttpGet("{action}")]
public IActionResult GetTodoItems2() =>
ControllerContext.MyDisplayRouteInfo();
// Delete: api/TodoItems1/MyDelete2/5
[EnableCors("MyPolicy")]
[HttpDelete("{action}/{id}")]
public IActionResult MyDelete2(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
}
Delete [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] // 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);
}
可以通过将示例部署到 Azure 来测试上述代码。 在“控制器”下拉列表中,选择“预检”,然后再选择“设置控制器”。 对 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.htmlhttps://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 的终结点方面提供最佳控制。
Warning
UseCors 必须按正确的顺序调用。 有关详细信息,请参阅中间件顺序。 例如,使用 UseCors 时,必须在 UseResponseCaching 之前调用 UseResponseCaching。
以下各部分详细介绍了每种方法。
具有命名策略和中间件的 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 扩展方法并指定
_myAllowSpecificOriginsCORS 策略。UseCors添加 CORS 中间件。 对UseCors的调用必须放在UseRouting之后,但在UseAuthorization之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式 调用 AddCors。 Lambda 表达式接受一个 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins。 - 为所有控制器终结点启用
_myAllowSpecificOriginsCORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseCors 之前调用 UseResponseCaching。
使用终结点路由时,CORS 中间件 必须 配置为在调用 UseRouting 和 UseEndpoints 之间执行。
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,并且不返回任何标头。
Warning
UseCors 必须位于 UseRouting 之后和 UseAuthorization 之前。 这是为了确保 CORS 标头包含在已授权和未经授权的调用的响应中。
UseCors 和 UseStaticFiles 的顺序
通常,在 UseStaticFiles 之前调用 UseCors。 使用 JavaScript 跨站点检索静态文件的应用必须在 UseCors 之前调用 UseStaticFiles。
具有默认策略和中间件的 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和控制器终结点允许使用指定策略的跨源请求。 -
/echo2和 Razor Pages 终结点不允许跨源请求,因为未指定默认策略。
[DisableCors] 属性不会禁用由终结点路由结合 RequireCors 启用的 CORS。
在 .NET 7 中,属性 [EnableCors] 必须传递参数,或者从路由上的不明确匹配生成 ASP0023 警告。 .NET 8 或更高版本不会生成 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 页
PageModel - Controller
- 控制器操作方法
可将不同的策略应用于具有 [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
[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 策略中设置的各种选项:
AddPolicy 在 Program.cs 中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。
设置允许的来源
AllowAnyOrigin:允许来自所有源且使用任意方案(http 或 https)的 CORS 请求。
AllowAnyOrigin 不安全,因为任何网站都可以向应用发出跨源请求。
Note
指定 AllowAnyOrigin 和 AllowCredentials 是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,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();
在前面的代码中, SetIsOriginAllowedToAllowWildcardSubdomains 使用通配符源 "https://*.example.com"调用。 此配置允许来自example.com的任何子域(例如https://subdomain.example.com或https://api.example.com)的 CORS 请求。 必须在 * 源中包含通配符才能启用通配符子域匹配。
设置允许的 HTTP 方法
- 允许任何 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 中所述的标头完全匹配时,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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
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 头,则浏览器不会向应用公开响应,而且跨源请求会失败。
允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。
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-urlencodedmultipart/form-datatext/plain
为客户端请求设置的请求标头规则适用于应用通过在 XMLHttpRequest 对象上调用 setRequestHeader 来设置的这些标头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent、Host 或 Content-Length。
以下是一个响应示例,与本文档Test 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 方法。 可包括以下标头:
- Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
-
Access-Control-Request-Headers:应用在实际请求上设置的请求头的列表。 如前文所述,这不包含浏览器设置的标头,如
User-Agent。 - Access-Control-Allow-Methods
如果预检请求被拒绝,应用将返回一个 200 OK 响应,但不会设置 CORS 标头。 因此,浏览器不会尝试跨源请求。 如需查看被拒绝的预检请求示例,请参阅本文档中的测试 CORS部分。
使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器:
- Firefox:跨源请求被阻止:同源策略不允许读取
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5上的远程资源。 (原因:CORS 请求不成功)。 了解更多信息 - 基于 Chromium:从源“https://cors3.azurewebsites.net”访问“https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5”的请求已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:请求的资源中不存在“Access-Control-Allow-Origin”标头。 如果不透明响应能够满足你的需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下获取该资源。
若要允许特定标头,请调用 WithHeaders:
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();
要允许所有 授权请求标头,请调用 AllowAnyHeader:
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 的方式不一致。 如果以下任一项:
- 标头被设置为除
"*"之外的任意值 - 称为 AllowAnyHeader:至少包括
Accept、Content-Type和Origin,以及任何你想支持的自定义标头。
自动预检请求代码
当以以下任一方式应用 CORS 策略时:
- 通过在
Program.cs中调用app.UseCors,在全局范围内生效。 - 使用
[EnableCors]属性。
ASP.NET Core 响应预检 OPTIONS 请求。
本文档的测试 CORS 部分演示了这种行为。
用于预检请求的 [HttpOptions] 属性
当使用适当的策略启用 CORS 时,ASP.NET Core 通常会自动响应 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);
}
有关测试上述代码的说明,请参阅使用 [EnableCors] 属性和 RequireCors 方法测试 CORS。
设置预检过期时间
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
CORS 的工作原理
本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。
- CORS 不是一项安全功能。 CORS 是一种 W3C 标准,允许服务器放宽同源策略。
- 例如,恶意行为者可能对你的网站使用跨站脚本 (XSS),并向其启用了 CORS 的网站执行跨站请求以窃取信息。
- 允许 CORS 并不会使 API 更安全。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- Fiddler
- .NET HttpClient
- 通过在地址栏中输入 URL 的 Web 浏览器。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- 通过这种方式,服务器允许浏览器执行跨源 XHR 或 Fetch API 请求,否则会被禁止。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
<script>标记来接收响应。 允许跨源加载脚本。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,就会自动为跨源请求设置这些标头。 启用 CORS 不需要自定义 JavaScript 代码。
在已部署的示例上选择“PUT”测试按钮。
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
在前面的 响应标头 中,服务器在响应中设置了 Access-Control-Allow-Origin 标头。 此头的 https://cors1.azurewebsites.net 值与请求中的 Origin 头一致。
如果调用 AllowAnyOrigin,则返回通配符值 Access-Control-Allow-Origin: *。
AllowAnyOrigin 允许任意来源。
如果响应不包含 Access-Control-Allow-Origin 头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。
从 HTTP 重定向到 HTTPS 会导致 CORS 预检请求出现 ERR_INVALID_REDIRECT 错误
使用 HTTP 向终结点发出的请求在被 UseHttpsRedirection 重定向到 HTTPS 时会失败,并出现 ERR_INVALID_REDIRECT on the CORS preflight request。
API 项目可以拒绝 HTTP 请求,而不是使用 UseHttpsRedirection 将请求重定向到 HTTPS。
IIS 中的 CORS
部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。
测试 CORS
示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个已添加 Razor 页面的 API 项目:
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();
Warning
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();
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
使用以下方法之一测试上述示例代码:
- 使用
dotnet run运行该示例,并使用https://localhost:5001的默认 URL。 - 从 Visual Studio 运行示例,并针对
https://localhost:44398的 URL 将端口设置为 44398。
使用带有 F12 工具的浏览器:
单击值按钮,并在网络选项卡中查看标头。
选择PUT test按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT 测试会创建两个请求:一个 OPTIONS 预检请求和一个 PUT 请求。
选择此
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 的情况下获取该资源。
启用了 CORS 的终结点可以使用 curl 或 Fiddler 等工具进行测试。 使用工具时,Origin 头指定的请求源必须与接收请求的主机不同。 如果根据 Origin 标头的值,请求不属于跨源,则:
- 无需 CORS 中间件来处理请求。
- 响应中未返回 CORS 标头。
以下命令使用 curl 发出包含信息的 OPTIONS 请求:
curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i
使用 [EnableCors] 属性和 RequireCors 方法测试 CORS
请考虑以下代码,该代码使用终结点路由,通过 RequireCors 按终结点启用 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.UseEndpoints(endpoints =>
{
endpoints.MapGet("/echo",
context => context.Response.WriteAsync("echo"))
.RequireCors("MyPolicy");
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.Run();
请注意,只有 /echo 终结点使用 RequireCors 来允许使用指定策略的跨域请求。 下面的控制器使用 [EnableCors] 属性来启用 CORS。
以下 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("MyPolicy")]
[HttpGet("{action}")]
public IActionResult GetTodoItems2() =>
ControllerContext.MyDisplayRouteInfo();
// Delete: api/TodoItems1/MyDelete2/5
[EnableCors("MyPolicy")]
[HttpDelete("{action}/{id}")]
public IActionResult MyDelete2(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
}
Delete [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] // 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);
}
可以通过将示例部署到 Azure 来测试上述代码。在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 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.htmlhttps://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 的终结点方面提供最佳控制。
Warning
UseCors 必须按正确的顺序调用。 有关详细信息,请参阅中间件顺序。 例如,使用 UseCors 时,必须在 UseResponseCaching 之前调用 UseResponseCaching。
以下各部分详细介绍了每种方法。
具有命名策略和中间件的 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 扩展方法并指定
_myAllowSpecificOriginsCORS 策略。UseCors添加 CORS 中间件。 对UseCors的调用必须放在UseRouting之后,但在UseAuthorization之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式 调用 AddCors。 Lambda 表达式接受一个 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins。 - 为所有控制器终结点启用
_myAllowSpecificOriginsCORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseCors 之前调用 UseResponseCaching。
使用终结点路由时,CORS 中间件 必须 配置为在调用 UseRouting 和 UseEndpoints 之间执行。
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,并且不返回任何标头。
Warning
UseCors 必须位于 UseRouting 之后和 UseAuthorization 之前。 这是为了确保 CORS 标头包含在已授权和未经授权的调用的响应中。
UseCors 和 UseStaticFiles 的顺序
通常,在 UseStaticFiles 之前调用 UseCors。 使用 JavaScript 跨站点检索静态文件的应用必须在 UseCors 之前调用 UseStaticFiles。
具有默认策略和中间件的 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。
对于终结点路由,可以使用 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和控制器终结点允许使用指定策略的跨源请求。 -
/echo2和 Razor Pages 终结点不允许跨源请求,因为未指定默认策略。
[DisableCors] 属性不会禁用由终结点路由结合 RequireCors 启用的 CORS。
有关测试与前面代码类似的代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。
[EnableCors] 属性为所选终结点(而不是所有终结点)启用 CORS:
-
[EnableCors]指定默认策略。 -
[EnableCors("{Policy String}")]指定命名策略。
[EnableCors] 属性可应用于:
-
Razor 页
PageModel - Controller
- 控制器操作方法
可将不同的策略应用于具有 [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
[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.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();
}
前面的代码:
- 不通过终结点路由启用 CORS。
- 不定义默认 CORS 策略。
- 使用 [EnableCors("MyPolicy")] 为控制器启用
"MyPolicy"CORS 策略。 - 禁用
GetValues2方法的 CORS。
有关测试前面代码的说明,请参阅测试 CORS。
CORS 策略选项
本部分介绍可以在 CORS 策略中设置的各种选项:
AddPolicy 在 Program.cs 中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。
设置允许的来源
AllowAnyOrigin:允许来自所有源且使用任意方案(http 或 https)的 CORS 请求。
AllowAnyOrigin 不安全,因为任何网站都可以向应用发出跨源请求。
Note
指定 AllowAnyOrigin 和 AllowCredentials 是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,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();
在前面的代码中, SetIsOriginAllowedToAllowWildcardSubdomains 使用通配符源 "https://*.example.com"调用。 此配置允许来自example.com的任何子域(例如https://subdomain.example.com或https://api.example.com)的 CORS 请求。 必须在 * 源中包含通配符才能启用通配符子域匹配。
设置允许的 HTTP 方法
- 允许任何 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 中所述的标头完全匹配时,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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
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 头,则浏览器不会向应用公开响应,而且跨源请求会失败。
允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。
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-urlencodedmultipart/form-datatext/plain
为客户端请求设置的请求标头规则适用于应用通过在 XMLHttpRequest 对象上调用 setRequestHeader 来设置的这些标头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent、Host 或 Content-Length。
以下是一个响应示例,与本文档Test 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 方法。 可包括以下标头:
- Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
-
Access-Control-Request-Headers:应用在实际请求上设置的请求头的列表。 如前文所述,这不包含浏览器设置的标头,如
User-Agent。 - Access-Control-Allow-Methods
如果预检请求被拒绝,应用将返回一个 200 OK 响应,但不会设置 CORS 标头。 因此,浏览器不会尝试跨源请求。 如需查看被拒绝的预检请求示例,请参阅本文档中的测试 CORS部分。
使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器:
- Firefox:跨源请求被阻止:同源策略不允许读取
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5上的远程资源。 (原因:CORS 请求不成功)。 了解更多信息 - 基于 Chromium:从源“https://cors3.azurewebsites.net”访问“https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5”的请求已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:请求的资源中不存在“Access-Control-Allow-Origin”标头。 如果不透明响应能够满足你的需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下获取该资源。
若要允许特定标头,请调用 WithHeaders:
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();
要允许所有 授权请求标头,请调用 AllowAnyHeader:
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 的方式不一致。 如果以下任一项:
- 标头被设置为除
"*"之外的任意值 - 称为 AllowAnyHeader:至少包括
Accept、Content-Type和Origin,以及任何你想支持的自定义标头。
自动预检请求代码
当以以下任一方式应用 CORS 策略时:
- 通过在
Program.cs中调用app.UseCors,在全局范围内生效。 - 使用
[EnableCors]属性。
ASP.NET Core 响应预检 OPTIONS 请求。
使用 RequireCors 基于每个终结点启用 CORS 目前不支持自动预检请求。
本文档的测试 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:
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 的工作原理
本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。
- CORS 不是一项安全功能。 CORS 是一种 W3C 标准,允许服务器放宽同源策略。
- 例如,恶意行为者可能对你的网站使用跨站脚本 (XSS),并向其启用了 CORS 的网站执行跨站请求以窃取信息。
- 允许 CORS 并不会使 API 更安全。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- Fiddler
- .NET HttpClient
- 通过在地址栏中输入 URL 的 Web 浏览器。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- 通过这种方式,服务器允许浏览器执行跨源 XHR 或 Fetch API 请求,否则会被禁止。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
<script>标记来接收响应。 允许跨源加载脚本。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,就会自动为跨源请求设置这些标头。 启用 CORS 不需要自定义 JavaScript 代码。
以下是从 Values 测试按钮到 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按钮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
在前面的 响应标头 中,服务器在响应中设置了 Access-Control-Allow-Origin 标头。 此头的 https://cors1.azurewebsites.net 值与请求中的 Origin 头一致。
如果调用 AllowAnyOrigin,则返回通配符值 Access-Control-Allow-Origin: *。
AllowAnyOrigin 允许任意来源。
如果响应不包含 Access-Control-Allow-Origin 头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。
从 HTTP 重定向到 HTTPS 会导致 CORS 预检请求出现 ERR_INVALID_REDIRECT 错误
使用 HTTP 向终结点发出的请求在被 UseHttpsRedirection 重定向到 HTTPS 时会失败,并出现 ERR_INVALID_REDIRECT on the CORS preflight request。
API 项目可以拒绝 HTTP 请求,而不是使用 UseHttpsRedirection 将请求重定向到 HTTPS。
显示 OPTIONS 请求
默认情况下,Chrome 和 Microsoft Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 要在这些浏览器中显示 OPTIONS 请求,请执行以下操作:
-
chrome://flags/#out-of-blink-cors或edge://flags/#out-of-blink-cors - 禁用该标志。
- restart.
Firefox 默认显示 OPTIONS 请求。
IIS 中的 CORS
部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。
测试 CORS
示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个已添加 Razor 页面的 API 项目:
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();
Warning
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();
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
使用以下方法之一测试上述示例代码:
- 使用
dotnet run运行该示例,并使用https://localhost:5001的默认 URL。 - 从 Visual Studio 运行示例,并针对
https://localhost:44398的 URL 将端口设置为 44398。
使用带有 F12 工具的浏览器:
单击值按钮,并在网络选项卡中查看标头。
选择PUT test按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT 测试会创建两个请求:一个 OPTIONS 预检请求和一个 PUT 请求。
选择此
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 的情况下获取该资源。
启用了 CORS 的终结点可以使用 curl 或 Fiddler 等工具进行测试。 使用工具时,Origin 头指定的请求源必须与接收请求的主机不同。 如果根据 Origin 标头的值,请求不属于跨源,则:
- 无需 CORS 中间件来处理请求。
- 响应中未返回 CORS 标头。
以下命令使用 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);
}
在已部署的示例的测试页(https://cors1.azurewebsites.net/test?number=1)中测试上述代码。
Delete [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);
}
可以通过将示例部署到 Azure 来测试上述代码。在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 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.htmlhttps://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 的终结点方面提供最佳控制。
Warning
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,
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 扩展方法并指定
_myAllowSpecificOriginsCORS 策略。UseCors添加 CORS 中间件。 对UseCors的调用必须放在UseRouting之后,但在UseAuthorization之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式 调用 AddCors。 Lambda 表达式接受一个 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins。 - 为所有控制器终结点启用
_myAllowSpecificOriginsCORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseCors 之前调用 UseResponseCaching。
使用终结点路由时,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。
对于终结点路由,可以使用 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和控制器终结点允许使用指定策略的跨源请求。 -
/echo2和 Razor Pages 终结点不允许跨源请求,因为未指定默认策略。
[DisableCors] 属性不会禁用由终结点路由结合 RequireCors 启用的 CORS。
有关测试与前面代码类似的代码的说明,请参阅使用终结点路由和 [HttpOptions] 测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。
[EnableCors] 属性为所选终结点(而不是所有终结点)启用 CORS:
-
[EnableCors]指定默认策略。 -
[EnableCors("{Policy String}")]指定命名策略。
[EnableCors] 属性可应用于:
-
Razor 页
PageModel - Controller
- 控制器操作方法
可将不同的策略应用于具有 [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",
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();
});
}
}
为了最精细地控制 CORS 请求的限制:
- 将
[EnableCors("MyPolicy")]与命名策略一起使用。 - 不要定义默认策略。
- 不要使用终结点路由。
下一部分中的代码符合前面的列表。
有关测试与前面代码类似的代码的说明,请参阅测试 CORS。
禁用 CORS
[DisableCors] 属性不会禁用终结点路由已启用的 CORS。
以下代码定义 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();
}
前面的代码:
- 不通过终结点路由启用 CORS。
- 不定义默认 CORS 策略。
- 使用 [EnableCors("MyPolicy")] 为控制器启用
"MyPolicy"CORS 策略。 - 禁用
GetValues2方法的 CORS。
有关测试前面代码的说明,请参阅测试 CORS。
CORS 策略选项
本部分介绍可以在 CORS 策略中设置的各种选项:
AddPolicy 在 Startup.ConfigureServices 中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。
设置允许的来源
AllowAnyOrigin:允许来自所有源且使用任意方案(http 或 https)的 CORS 请求。
AllowAnyOrigin 不安全,因为任何网站都可以向应用发出跨源请求。
Note
指定 AllowAnyOrigin 和 AllowCredentials 是不安全的配置,可能会导致跨网站请求伪造。 同时使用这两种方法来配置应用时,CORS 服务会返回无效的 CORS 响应。
AllowAnyOrigin 会影响预检请求和 Access-Control-Allow-Origin 标头。 有关详细信息,请参阅预检请求部分。
SetIsOriginAllowedToAllowWildcardSubdomains:将策略的 IsOriginAllowed 属性设置为一个函数,当计算是否允许源时,此函数允许源匹配已配置的通配符域。
options.AddPolicy("MyAllowSubdomainPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
在前面的代码中, SetIsOriginAllowedToAllowWildcardSubdomains 使用通配符源 "https://*.example.com"调用。 此配置允许来自example.com的任何子域(例如https://subdomain.example.com或https://api.example.com)的 CORS 请求。 必须在 * 源中包含通配符才能启用通配符子域匹配。
设置允许的 HTTP 方法
- 允许任何 HTTP 方法:
- 影响预检请求和
Access-Control-Allow-Methods标头。 有关详细信息,请参阅预检请求部分。
设置允许的请求头
要允许在称为作者请求头的 CORS 请求中发送特定头,请调用 WithHeaders 并指定允许的头:
options.AddPolicy("MyAllowHeadersPolicy",
policy =>
{
// requires using Microsoft.Net.Http.Headers;
policy.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
要允许所有 授权请求标头,请调用 AllowAnyHeader:
options.AddPolicy("MyAllowAllHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.AllowAnyHeader();
});
AllowAnyHeader 影响预检请求和 Access-Control-Request-Headers 头。 有关详细信息,请参阅预检请求部分。
只有当在 Access-Control-Request-Headers 中发送的标头与 WithHeaders 中所述的标头完全匹配时,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-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma
CORS 规范将这些标头称为 简单响应标头。 要使应用能够使用其他标头,请调用 WithExposedHeaders:
options.AddPolicy("MyExposeResponseHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.WithExposedHeaders("x-custom-header");
});
跨源请求中的凭据
在 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:
options.AddPolicy("MyMyAllowCredentialsPolicy",
policy =>
{
policy.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-urlencodedmultipart/form-datatext/plain
为客户端请求设置的请求标头规则适用于应用通过在 XMLHttpRequest 对象上调用 setRequestHeader 来设置的这些标头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent、Host 或 Content-Length。
以下是一个响应示例,与本文档Test 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 方法。 可包括以下标头:
- Access-Control-Request-Method:将用于实际请求的 HTTP 方法。
-
Access-Control-Request-Headers:应用在实际请求上设置的请求头的列表。 如前文所述,这不包含浏览器设置的标头,如
User-Agent。 - Access-Control-Allow-Methods
如果预检请求被拒绝,应用将返回一个 200 OK 响应,但不会设置 CORS 标头。 因此,浏览器不会尝试跨源请求。 如需查看被拒绝的预检请求示例,请参阅本文档中的测试 CORS部分。
使用 F12 工具时,控制台应用会显示类似于以下内容之一的错误,具体取决于浏览器:
- Firefox:跨源请求被阻止:同源策略不允许读取
https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5上的远程资源。 (原因:CORS 请求不成功)。 了解更多信息 - 基于 Chromium:从源“https://cors3.azurewebsites.net”访问“https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5”的请求已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:请求的资源中不存在“Access-Control-Allow-Origin”标头。 如果不透明响应能够满足你的需求,请将请求的模式设置为“no-cors”,以便在禁用 CORS 的情况下获取该资源。
若要允许特定标头,请调用 WithHeaders:
options.AddPolicy("MyAllowHeadersPolicy",
policy =>
{
// requires using Microsoft.Net.Http.Headers;
policy.WithOrigins("http://example.com")
.WithHeaders(HeaderNames.ContentType, "x-custom-header");
});
要允许所有 授权请求标头,请调用 AllowAnyHeader:
options.AddPolicy("MyAllowAllHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.AllowAnyHeader();
});
浏览器设置 Access-Control-Request-Headers 的方式不一致。 如果以下任一项:
- 标头被设置为除
"*"之外的任意值 - 称为 AllowAnyHeader:至少包括
Accept、Content-Type和Origin,以及任何你想支持的自定义标头。
自动预检请求代码
当以以下任一方式应用 CORS 策略时:
- 通过在
Startup.Configure中调用app.UseCors,在全局范围内生效。 - 使用
[EnableCors]属性。
ASP.NET Core 响应预检 OPTIONS 请求。
使用 RequireCors 基于每个终结点启用 CORS 目前不支持自动预检请求。
本文档的测试 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",
policy =>
{
policy.WithOrigins("http://example.com")
.SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
});
CORS 的工作原理
本节介绍 HTTP 消息级别的 CORS 请求中发生的情况。
- CORS 不是一项安全功能。 CORS 是一种 W3C 标准,允许服务器放宽同源策略。
- 例如,恶意行为者可能对你的网站使用跨站脚本 (XSS),并向其启用了 CORS 的网站执行跨站请求以窃取信息。
- 允许 CORS 并不会使 API 更安全。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- Fiddler
- .NET HttpClient
- 通过在地址栏中输入 URL 的 Web 浏览器。
- 由客户端(浏览器)执行 CORS。 服务器执行请求并返回响应,客户端返回错误并阻止响应。 例如,以下任何工具都会显示服务器响应:
- 通过这种方式,服务器允许浏览器执行跨源 XHR 或 Fetch API 请求,否则会被禁止。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
<script>标记来接收响应。 允许跨源加载脚本。
- 没有 CORS 的浏览器无法执行跨源请求。 在 CORS 之前,使用 JSONP 来规避此限制。 JSONP 不使用 XHR,它使用
CORS 规范介绍了几个新的 HTTP 标头,它们支持跨源请求。 如果浏览器支持 CORS,就会自动为跨源请求设置这些标头。 启用 CORS 不需要自定义 JavaScript 代码。
以下是从 Values 测试按钮到 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按钮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
在前面的 响应标头 中,服务器在响应中设置了 Access-Control-Allow-Origin 标头。 此头的 https://cors1.azurewebsites.net 值与请求中的 Origin 头一致。
如果调用 AllowAnyOrigin,则返回通配符值 Access-Control-Allow-Origin: *。
AllowAnyOrigin 允许任意来源。
如果响应不包含 Access-Control-Allow-Origin 头,则跨源请求失败。 具体来说,浏览器不允许该请求。 即使服务器返回成功的响应,浏览器也不会将响应提供给客户端应用。
显示 OPTIONS 请求
默认情况下,Chrome 和 Microsoft Edge 浏览器不会在 F12 工具的网络选项卡上显示 OPTIONS 请求。 要在这些浏览器中显示 OPTIONS 请求,请执行以下操作:
-
chrome://flags/#out-of-blink-cors或edge://flags/#out-of-blink-cors - 禁用该标志。
- restart.
Firefox 默认显示 OPTIONS 请求。
IIS 中的 CORS
部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。
测试 CORS
示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个已添加 Razor 页面的 API 项目:
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();
});
}
}
Warning
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();
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
使用以下方法之一测试上述示例代码:
- 使用
dotnet run运行该示例,并使用https://localhost:5001的默认 URL。 - 从 Visual Studio 运行示例,并针对
https://localhost:44398的 URL 将端口设置为 44398。
使用带有 F12 工具的浏览器:
单击值按钮,并在网络选项卡中查看标头。
选择PUT test按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT 测试会创建两个请求:一个 OPTIONS 预检请求和一个 PUT 请求。
选择此
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 的情况下获取该资源。
启用了 CORS 的终结点可以使用 curl 或 Fiddler 等工具进行测试。 使用工具时,Origin 头指定的请求源必须与接收请求的主机不同。 如果根据 Origin 标头的值,请求不属于跨源,则:
- 无需 CORS 中间件来处理请求。
- 响应中未返回 CORS 标头。
以下命令使用 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);
}
在已部署的示例的测试页(https://cors1.azurewebsites.net/test?number=1)中测试上述代码。
Delete [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);
}
可以通过将示例部署到 Azure 来测试上述代码。在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 TodoItems2Controller 终结点的所有 CORS 调用都成功。