来源: 在 ASP.NET Core 中启用跨源请求 (CORS) | Microsoft Learn
作者:Rick Anderson 和 Kirk Larkin
本文介绍如何在 ASP.NET Core 应用中启用 CORS。
浏览器安全性可防止网页向不处理网页的域发送请求。 此限制称为同域策略。 同域策略可防止恶意站点从另一站点读取敏感数据。 有时,你可能希望允许其他网站向自己的应用发出跨源请求。 有关详细信息,请参阅 Mozilla CORS 文章。
跨源资源共享 (CORS):
- 是一种 W3C 标准,允许服务器放宽同源策略。
- 不是安全功能,CORS 放松了安全限制。 允许 CORS 并不会使 API 更安全。 有关详细信息,请参阅 CORS 的工作原理。
- 允许服务器显式允许某些跨源请求,同时拒绝其他请求。
- 比早期技术(如 JSONP)更安全、更灵活。
同源
如果两个 URL 具有相同的方案、主机和端口 (RFC 6454),则它们同源。
这两个 URL 同源:
https://example.com/foo.html
https://example.com/bar.html
这些 URL 的源与前两个 URL 不同:
https://example.net
:不同的域https://www.example.com/foo.html
:不同的子域http://example.com/foo.html
:不同的方案https://example.com:9000/foo.html
:不同的端口
启用 CORS
有三种方法可以启用 CORS:
- 在使用命名策略或默认策略的中间件中。
- 使用终结点路由。
- 使用 [EnableCors] 属性。
将 [EnableCors] 属性与命名策略一起使用可在限制支持 CORS 的终结点方面提供最佳控制。
警告
UseCors 必须按正确的顺序调用。 有关详细信息,请参阅中间件顺序。 例如,使用 UseResponseCaching
时,必须在 UseResponseCaching 之前调用 UseCors
。
以下各部分详细介绍了每种方法。
具有命名策略和中间件的 CORS
CORS 中间件处理跨源请求。 以下代码将 CORS 策略应用于具有指定源的所有应用终结点:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
// services.AddResponseCaching();
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
前面的代码:
- 将策略名称设置为
_myAllowSpecificOrigins
。 策略名称是任意的。 - 调用 UseCors 扩展方法并指定
_myAllowSpecificOrigins
CORS 策略。UseCors
添加 CORS 中间件。 对UseCors
的调用必须放在UseRouting
之后,但在UseAuthorization
之前。 有关详细信息,请参阅中间件顺序。 - 使用 lambda 表达式调用 AddCors。 lambda 采用 CorsPolicyBuilder 对象。 本文稍后将介绍配置选项,如
WithOrigins
。 - 为所有控制器终结点启用
_myAllowSpecificOrigins
CORS 策略。 要将 CORS 策略应用于特定终结点,请参阅终结点路由。 - 使用响应缓存中间件时,请在 UseResponseCaching 之前调用 UseCors。
对于终结点路由,CORS 中间件必须配置为在对 UseRouting
和 UseEndpoints
的调用之间执行。
有关测试与前面代码类似的代码的说明,请参阅测试 CORS。
AddCors 方法调用将 CORS 服务添加到应用的服务容器:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
// services.AddResponseCaching();
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
有关详细信息,请参阅本文档中的 CORS 策略选项。
可以链接 CorsPolicyBuilder 方法,如以下代码所示:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(MyAllowSpecificOrigins);
app.UseAuthorization();
app.MapControllers();
app.Run();
注意:指定的 URL 不能包含尾部斜杠 (/
)。 如果 URL 以 /
结尾,则比较返回 false
,并且不返回任何标头。
警告
UseCors
必须位于 UseRouting
之后和 UseAuthorization
之前。 这是为了确保 CORS 标头包含在已授权和未经授权的调用的响应中。
UseCors 和 UseStaticFiles 顺序
通常,在 UseCors
之前调用 UseStaticFiles
。 使用 JavaScript 跨站点检索静态文件的应用必须在 UseStaticFiles
之前调用 UseCors
。
具有默认策略和中间件的 CORS
以下突出显示的代码启用默认 CORS 策略:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.Run();
前面的代码将默认 CORS 策略应用于所有控制器终结点。
通过终结点路由启用 Cors
对于终结点路由,可以使用 RequireCors 扩展方法集基于每个终结点启用 CORS:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com");
});
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/echo",
context => context.Response.WriteAsync("echo"))
.RequireCors(MyAllowSpecificOrigins);
endpoints.MapControllers()
.RequireCors(MyAllowSpecificOrigins);
endpoints.MapGet("/echo2",
context => context.Response.WriteAsync("echo2"));
endpoints.MapRazorPages();
});
app.Run();
在上述代码中:
app.UseCors
启用 CORS 中间件。 由于尚未配置默认策略,因此单独的app.UseCors()
不会启用 CORS。/echo
和控制器终结点允许使用指定策略的跨源请求。/echo2
和 Razor Pages 终结点不允许跨源请求,因为未指定默认策略。
[DisableCors] 属性不会禁用终结点路由已使用 RequireCors
启用的 CORS。
在 ASP.NET Core 7.0 中,[EnableCors]
属性必须传递参数,否则系统会根据路由上的不明确匹配生成 ASP0023 警告。 ASP.NET Core 8.0 及更高版本不会生成 ASP0023
警告。
[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
// OPTIONS: api/TodoItems2/5
[HttpOptions("{id}")]
public IActionResult PreflightRoute(int id)
{
return NoContent();
}
// OPTIONS: api/TodoItems2
[HttpOptions]
public IActionResult PreflightRoute()
{
return NoContent();
}
[HttpPut("{id}")]
public IActionResult PutTodoItem(int id)
{
if (id < 1)
{
return BadRequest();
}
return ControllerContext.MyDisplayRouteInfo(id);
}
// [EnableCors] // Not needed as OPTIONS path provided.
[HttpDelete("{id}")]
public IActionResult MyDelete(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
// [EnableCors] // Warning ASP0023 Route '{id}' conflicts with another action route.
// An HTTP request that matches multiple routes results in an ambiguous
// match error.
[EnableCors("MyPolicy")] // Required for this path.
[HttpGet]
public IActionResult GetTodoItems() =>
ControllerContext.MyDisplayRouteInfo();
[HttpGet("{action}")]
public IActionResult GetTodoItems2() =>
ControllerContext.MyDisplayRouteInfo();
[EnableCors("MyPolicy")] // Required for this path.
[HttpDelete("{action}/{id}")]
public IActionResult MyDelete2(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
}
有关测试与前面代码类似的代码的说明,请参阅使用 [EnableCors] 属性和 RequireCors 方法测试 CORS。
使用属性启用 CORS
使用 [EnableCors] 属性启用 CORS,并仅对需要 CORS 的终结点应用命名策略可提供最佳控制。
[EnableCors] 属性提供了全局应用 CORS 的替代方法。 [EnableCors]
属性为所选终结点(而不是所有终结点)启用 CORS:
[EnableCors]
指定默认策略。[EnableCors("{Policy String}")]
指定命名策略。
[EnableCors]
属性可应用于:
- Razor Page
PageModel
- 控制器
- 控制器操作方法
可将不同的策略应用于具有 [EnableCors]
属性的控制器、页面模型或操作方法。 如果将 [EnableCors]
属性应用于控制器、页面模型或操作方法,并且在中间件中启用了 CORS,则会应用两种策略。 建议不要合并策略。 使用 [EnableCors]
属性或中间件,两者不能位于同一应用中。
以下代码对每种方法应用不同的策略:
[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
// GET api/values
[EnableCors("AnotherPolicy")]
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "green widget", "red widget" };
}
// GET api/values/5
[EnableCors("Policy1")]
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return id switch
{
1 => "green widget",
2 => "red widget",
_ => NotFound(),
};
}
}
以下代码创建两个 CORS 策略:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("Policy1",
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com");
});
options.AddPolicy("AnotherPolicy",
policy =>
{
policy.WithOrigins("http://www.contoso.com")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.Run();
为了最精细地控制 CORS 请求的限制:
- 将
[EnableCors("MyPolicy")]
与命名策略一起使用。 - 不要定义默认策略。
- 不要使用终结点路由。
下一部分中的代码符合前面的列表。
有关测试与前面代码类似的代码的说明,请参阅测试 CORS。
禁用 CORS
[DisableCors] 属性不会禁用终结点路由已启用的 CORS。
以下代码定义 CORS 策略 "MyPolicy"
:
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com")
.WithMethods("PUT", "DELETE", "GET");
});
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.UseEndpoints(endpoints => {
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.Run();
以下代码为 GetValues2
操作禁用 CORS:
[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public IActionResult Get() =>
ControllerContext.MyDisplayRouteInfo();
// GET api/values/5
[HttpGet("{id}")]
public IActionResult Get(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
// PUT api/values/5
[HttpPut("{id}")]
public IActionResult Put(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
// GET: api/values/GetValues2
[DisableCors]
[HttpGet("{action}")]
public IActionResult GetValues2() =>
ControllerContext.MyDisplayRouteInfo();
}
前面的代码:
- 不通过终结点路由启用 CORS。
- 不定义默认 CORS 策略。
- 使用 [EnableCors(“MyPolicy”)] 为控制器启用
"MyPolicy"
CORS 策略。 - 为
GetValues2
方法禁用 CORS。
有关测试前面代码的说明,请参阅测试 CORS。
CORS 策略选项
本部分介绍可以在 CORS 策略中设置的各种选项:
AddPolicy 在 Program.cs
中调用。 对于某些选项,先阅读 CORS 的工作原理部分可能会有所帮助。
设置允许的源
AllowAnyOrigin:允许具有任何方案(http
或 https
)的所有源的 CORS 请求。 AllowAnyOrigin
不安全,因为任何网站都可以向应用发出跨源请求。
备注
指定 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();
设置允许的 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
中所述的头完全匹配时,才能与 WithHeaders
指定的特定头匹配 CORS 中间件策略。
例如,考虑按如下方式配置的应用:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中间件拒绝具有以下请求头的预检请求,因为 Content-Language
(HeaderNames.ContentLanguage) 未列在 WithHeaders
中:
Access-Control-Request-Headers: Cache-Control, Content-Language
应用返回 200 OK 响应,但不发回 CORS 头。 因此,浏览器不会尝试跨源请求。
设置公开的响应头
默认情况下,浏览器不会向应用公开所有响应头。 有关详细信息,请参阅 W3C 跨源资源共享(术语):简单响应头。
默认情况下可用的响应头包括:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 规范将这些头称为简单响应头。 要使其他头可用于应用,请调用 WithExposedHeaders:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("MyExposeResponseHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.WithExposedHeaders("x-custom-header");
});
});
builder.Services.AddControllers();
var app = builder.Build();
跨源请求中的凭据
凭据需要在 CORS 请求中进行特殊处理。 默认情况下,浏览器不会使用跨源域请求发送凭据。 凭据包括 cookie 和 HTTP 身份验证方案。 要使用跨源请求发送凭据,客户端必须将 XMLHttpRequest.withCredentials
设置为 true
。
直接使用 XMLHttpRequest
:
var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;
使用 JQuery:
$.ajax({
type: 'get',
url: 'https://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
});
使用 Fetch API:
fetch('https://www.example.com/api/test', {
credentials: 'include'
});
服务器必须允许凭据。 要允许跨源凭据,请调用 AllowCredentials:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("MyMyAllowCredentialsPolicy",
policy =>
{
policy.WithOrigins("http://example.com")
.AllowCredentials();
});
});
builder.Services.AddControllers();
var app = builder.Build();
HTTP 响应包含一个 Access-Control-Allow-Credentials
头,它告诉浏览器服务器允许跨源请求的凭据。
如果浏览器发送凭据,但响应不包含有效的 Access-Control-Allow-Credentials
头,则浏览器不会向应用公开响应,而且跨源请求会失败。
允许跨源凭据会带来安全风险。 另一个域中的网站可以在用户不知情的情况下代表用户将登录用户的凭据发送到应用。
CORS 规范还指出,如果存在 Access-Control-Allow-Credentials
头,则将源设置为 "*"
(所有源)是无效的。
预检请求
对于某些 CORS 请求,浏览器会在发出实际请求之前发送额外的 OPTIONS 请求。 此请求称为预检请求。 如果满足以下所有条件,浏览器可以跳过预检请求:
- 请求方法为 GET、HEAD 或 POST。
- 应用不会设置
Accept
、Accept-Language
、Content-Language
、Content-Type
或Last-Event-ID
以外的请求头。 Content-Type
头(如果已设置)具有以下值之一:application/x-www-form-urlencoded
multipart/form-data
text/plain
为客户端请求设置的请求头规则适用于应用通过在 XMLHttpRequest
对象上调用 setRequestHeader
设置的头。 CORS 规范将这些头称为作者请求头。 此规则不适用于浏览器可以设置的头,如 User-Agent
、Host
或 Content-Length
。
以下是与本文档测试 CORS 部分中的 [Put test] 按钮发出的预检请求类似的示例响应。
General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content
Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin
Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0
预检请求使用 HTTP OPTIONS 方法。 它可能包含以下头:
- 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
- Postman
- .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 test 按钮
以下是从 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
UseHttpsRedirection 使用 HTTP(但重定向到 HTTPS)对终结点进行的请求失败,并返回 ERR_INVALID_REDIRECT on the CORS preflight request
。
API 项目可以拒绝 HTTP 请求,而不是使用 UseHttpsRedirection
将请求重定向到 HTTPS。
IIS 中的 CORS
部署到 IIS 时,如果服务器未配置为允许匿名访问,则 CORS 必须在 Windows 身份验证之前运行。 要支持此方案,需要为应用安装和配置 IIS CORS 模块。
测试 CORS
示例下载包含测试 CORS 的代码。 请参阅如何下载。 该示例是一个 API 项目,其中添加了 Razor Pages:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy(name: "MyPolicy",
policy =>
{
policy.WithOrigins("http://example.com",
"http://www.contoso.com",
"https://cors1.azurewebsites.net",
"https://cors3.azurewebsites.net",
"https://localhost:44398",
"https://localhost:5001")
.WithMethods("PUT", "DELETE", "GET");
});
});
builder.Services.AddControllers();
builder.Services.AddRazorPages();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthorization();
app.MapControllers();
app.MapRazorPages();
app.Run();
警告
WithOrigins("https://localhost:<port>");
应仅用于测试类似于下载示例代码的示例代码。
以下 ValuesController
提供了用于测试的终结点:
[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public IActionResult Get() =>
ControllerContext.MyDisplayRouteInfo();
// GET api/values/5
[HttpGet("{id}")]
public IActionResult Get(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
// PUT api/values/5
[HttpPut("{id}")]
public IActionResult Put(int id) =>
ControllerContext.MyDisplayRouteInfo(id);
// GET: api/values/GetValues2
[DisableCors]
[HttpGet("{action}")]
public IActionResult GetValues2() =>
ControllerContext.MyDisplayRouteInfo();
}
MyDisplayRouteInfo 由 Rick.Docs.Samples.RouteInfo NuGet 包提供,会显示路由信息。
使用以下方法之一测试上述示例代码:
- 使用 https://cors3.azurewebsites.net/ 上部署的示例应用。 无需下载示例。
- 使用
https://localhost:5001
的默认 URL 运行带有dotnet run
的示例。 - 从 Visual Studio 运行示例,并针对
https://localhost:44398
的 URL 将端口设置为 44398。
使用带有 F12 工具的浏览器:
- 选择“值”按钮并在“网络”选项卡中查看头。
- 选择“PUT test”按钮。 有关显示 OPTIONS 请求的说明,请参阅显示 OPTIONS 请求。 PUT test 会创建两个请求,一个 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 或 Postman 等工具进行测试。 使用工具时,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);
}
从已部署示例的测试页测试前面的代码。 在“控制器”下拉列表中,选择“预检”,然后选择“设置控制器”。 对 TodoItems2Controller
终结点的所有 CORS 调用都成功。