在 ASP.NET Core 中啟用跨原始來源要求 (CORS)
注意
這不是這篇文章的最新版本。 如需目前的版本,請參閱 本文的 .NET 9 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
作者: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://contoso.example.com/foo.html
:不同的子網域http://example.com/foo.html
:不同的配置https://example.com:9000/foo.html
:不同的連接埠
啟用 CORS
有三種方式可以啟用 CORS:
- 在中介軟體中,使用具名原則或預設原則。
- 使用端點路由。
- 使用 [EnableCors] 屬性。
搭配具名原則使用 [EnableCors] 屬性能夠以最精細的程度來控制對於支援 CORS 之端點的限制。
警告
必須以正確的順序呼叫 UseCors。 如需詳細資訊,請參閱中介軟體順序。 例如,在使用 UseResponseCaching
時,必須先呼叫 UseCors
,再呼叫 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 擴充方法,並指定
_myAllowSpecificOrigins
CORS 原則。UseCors
會新增 CORS 中介軟體。 對UseCors
的呼叫必須放在UseRouting
之後、UseAuthorization
之前。 如需詳細資訊,請參閱中介軟體順序。 - 使用 Lambda 運算式呼叫 AddCors。 Lambda 會採用 CorsPolicyBuilder 物件。 本文稍後會說明組態選項,例如:
WithOrigins
。 - 為所有控制器端點啟用
_myAllowSpecificOrigins
CORS 原則。 請參閱端點路由以將 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 端點不允許跨原始來源要求,因為未指定預設原則。
如果 CORS 是由端點路由透過 RequireCors
所啟用的,則 [DisableCors] 屬性無法停用 CORS。
如需與上述程式碼類似的程式碼測試的指示,請參閱使用 [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 原則中設定的各種選項:
在 Program.cs
中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 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
中規定的標頭完全相符時,CORS 中介軟體原則才能與 WithHeaders
指定的特定標頭相符。
例如,假設某個應用程式的設定如下:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中介軟體會拒絕具有下列要求標頭的預檢要求,因為 Content-Language
(HeaderNames.ContentLanguage) 未在 WithHeaders
中列出:
Access-Control-Request-Headers: Cache-Control, Content-Language
應用程式會傳回 200 OK 回應,但不會將 CORS 標頭傳回。 因此,瀏覽器不會嘗試跨原始來源要求。
設定公開的回應標頭
根據預設,瀏覽器不會向應用程式公開所有回應標頭。 如需詳細資訊,請參閱 W3C 跨原始來源資源共用 (術語):簡單回應標頭。
預設可用的回應標頭為:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規格將此類標頭稱之為簡單回應標頭。 若要讓應用程式能夠使用其他標頭,請呼叫 WithExposedHeaders:
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 要求中進行特殊處理。 根據預設,瀏覽器不會傳送具有跨原始來源要求的認證。 認證包括 ookie 和 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
}
});
使用擷取 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
。
注意
這篇文章包含由範例程式碼部署至兩邊 Azure 網站建立的網站、https://cors3.azurewebsites.net
以及 https://cors.azurewebsites.net
。
以下是與本文件的測試 CORS 區段中 [Put 測試] 按鈕所發出預檢要求類似的範例回應。
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
。
如果預檢要求遭到拒絕,應用程式會傳迴 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 的網頁瀏覽器。
- 由用戶端 (瀏覽器) 強制執行 CORS。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
- 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR 或擷取 API 要求 (其他方式將被禁止)。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
<script>
標籤來接收回應。 允許跨原始來源載入指令碼。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
CORS 規格引進了數個新的 HTTP 標頭,可啟用跨原始來源要求。 如果瀏覽器支援 CORS,則瀏覽器會自動為跨原始來源要求設定這些標頭。 不需要自訂 JavaScript 程式碼即可啟用 CORS。
以下範例演示了從 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 向端點提出要求,這些 HTTP 會因為使用 ERR_INVALID_REDIRECT on the CORS preflight request
的 UseHttpsRedirection 失敗而重新導向至 HTTPS。
API 專案可以拒絕 HTTP 要求,而不是使用 UseHttpsRedirection
來將要求重新導向至 HTTPS。
IIS 中的 CORS
部署至 IIS 時,如果伺服器未設定為允許匿名存取,CORS 必須在 Windows 驗證之前執行。 若要支援此案例,必須為應用程式安裝並設定 IIS CORS 模組。
測試 CORS
範例下載包含用於測試 CORS 的程式碼。 請參閱如何下載。 此範例是新增了 Razor Pages 的 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();
警告
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 套件提供,並顯示路由資訊。
使用下列其中一種方法測試上述範例程式碼:
- 使用預設 URL
https://localhost:5001
執行dotnet run
指令以執行範例。 - 從 Visual Studio 執行範例,並將 URL
https://localhost:44398
的連接埠設定為 44398。
搭配 F12 工具使用瀏覽器:
選取 [值] 按鈕,然後檢閱 [網路] 索引標籤中的標頭。
選取 [PUT 測試] 按鈕。 如需顯示 OPTIONS 要求的指示,請參閱顯示 OPTIONS 要求。 PUT 測試會建立兩個要求:OPTIONS 預檢要求和 PUT 要求。
選取 [
GetValues2 [DisableCors]
] 按鈕以觸發失敗的 CORS 要求。 如檔中所述,回應會傳回 200 成功,但不會提出 CORS 要求。 選取 [主控台] 索引標籤以查看 CORS 錯誤。 視瀏覽器而定,會顯示類似下列的錯誤:CORS 原則已封鎖從來源
'https://cors3.azurewebsites.net'
中'https://cors1.azurewebsites.net/api/values/GetValues2'
擷取的存取權:要求的資源上沒有 '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.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
時,必須先呼叫 UseCors
,再呼叫 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 擴充方法,並指定
_myAllowSpecificOrigins
CORS 原則。UseCors
會新增 CORS 中介軟體。 對UseCors
的呼叫必須放在UseRouting
之後、UseAuthorization
之前。 如需詳細資訊,請參閱中介軟體順序。 - 使用 Lambda 運算式呼叫 AddCors。 Lambda 會採用 CorsPolicyBuilder 物件。 本文稍後會說明組態選項,例如:
WithOrigins
。 - 為所有控制器端點啟用
_myAllowSpecificOrigins
CORS 原則。 請參閱端點路由以將 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
必須放在 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 端點不允許跨原始來源要求,因為未指定預設原則。
如果 CORS 是由端點路由透過 RequireCors
所啟用的,則 [DisableCors] 屬性無法停用 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 原則中設定的各種選項:
在 Program.cs
中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 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
中規定的標頭完全相符時,CORS 中介軟體原則才能與 WithHeaders
指定的特定標頭相符。
例如,假設某個應用程式的設定如下:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中介軟體會拒絕具有下列要求標頭的預檢要求,因為 Content-Language
(HeaderNames.ContentLanguage) 未在 WithHeaders
中列出:
Access-Control-Request-Headers: Cache-Control, Content-Language
應用程式會傳回 200 OK 回應,但不會將 CORS 標頭傳回。 因此,瀏覽器不會嘗試跨原始來源要求。
設定公開的回應標頭
根據預設,瀏覽器不會向應用程式公開所有回應標頭。 如需詳細資訊,請參閱 W3C 跨原始來源資源共用 (術語):簡單回應標頭。
預設可用的回應標頭為:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規格將此類標頭稱之為簡單回應標頭。 若要讓應用程式能夠使用其他標頭,請呼叫 WithExposedHeaders:
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 要求中進行特殊處理。 根據預設,瀏覽器不會傳送具有跨原始來源要求的認證。 認證包括 ookie 和 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
}
});
使用擷取 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 測試] 按鈕所發出預檢要求類似的範例回應。
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 的網頁瀏覽器。
- 由用戶端 (瀏覽器) 強制執行 CORS。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
- 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR 或擷取 API 要求 (其他方式將被禁止)。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
<script>
標籤來接收回應。 允許跨原始來源載入指令碼。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
CORS 規格引進了數個新的 HTTP 標頭,可啟用跨原始來源要求。 如果瀏覽器支援 CORS,則瀏覽器會自動為跨原始來源要求設定這些標頭。 不需要自訂 JavaScript 程式碼即可啟用 CORS。
選取已部署範例上的 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 向端點提出要求,這些 HTTP 會因為使用 ERR_INVALID_REDIRECT on the CORS preflight request
的 UseHttpsRedirection 失敗而重新導向至 HTTPS。
API 專案可以拒絕 HTTP 要求,而不是使用 UseHttpsRedirection
來將要求重新導向至 HTTPS。
IIS 中的 CORS
部署至 IIS 時,如果伺服器未設定為允許匿名存取,CORS 必須在 Windows 驗證之前執行。 若要支援此案例,必須為應用程式安裝並設定 IIS CORS 模組。
測試 CORS
範例下載包含用於測試 CORS 的程式碼。 請參閱如何下載。 此範例是新增了 Razor Pages 的 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();
警告
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 套件提供,並顯示路由資訊。
使用下列其中一種方法測試上述範例程式碼:
- 使用預設 URL
https://localhost:5001
執行dotnet run
指令以執行範例。 - 從 Visual Studio 執行範例,並將 URL
https://localhost:44398
的連接埠設定為 44398。
搭配 F12 工具使用瀏覽器:
選取 [值] 按鈕,然後檢閱 [網路] 索引標籤中的標頭。
選取 [PUT 測試] 按鈕。 如需顯示 OPTIONS 要求的指示,請參閱顯示 OPTIONS 要求。 PUT 測試會建立兩個要求:OPTIONS 預檢要求和 PUT 要求。
選取 [
GetValues2 [DisableCors]
] 按鈕以觸發失敗的 CORS 要求。 如檔中所述,回應會傳回 200 成功,但不會提出 CORS 要求。 選取 [主控台] 索引標籤以查看 CORS 錯誤。 視瀏覽器而定,會顯示類似下列的錯誤:CORS 原則已封鎖從來源
'https://cors3.azurewebsites.net'
中'https://cors1.azurewebsites.net/api/values/GetValues2'
擷取的存取權:要求的資源上沒有 '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.In 控制器下拉式清單,選取[預檢],然後[設定控制器]。 所有對 TodoItems2Controller
端點的 CORS 呼叫都成功。
其他資源
作者:Rick Anderson 與 Kirk Larkin
本文說明如何在 ASP.NET Core 應用程式中啟用 CORS。
瀏覽器安全性可防止網頁向提供網頁的不同網域提出要求。 這項限制稱為相同來源原則。 相同來源原則會阻止惡意網站從另一個網站讀取敏感性資料。 有時候,您可能想要允許其他網站對您的應用程式提出跨原始來源要求。 如需詳細資訊,請參閱 Mozilla CORS 文章。
跨原始來源資源分享 (CORS):
- 這是一個 W3C 標準,可讓伺服器放寬相同原始來源原則。
- 不是安全性功能,CORS 會放寬安全性。 允許 CORS 並不會增強 API 的安全性。 如需詳細資訊,請參閱 CORS 的運作方式。
- 允許伺服器明確允許某些跨原始來源要求,同時拒絕其他要求。
- 遠比舊版技術,例如 JSONP更加安全,更有彈性。
檢視或下載範例程式碼 \(英文\) (如何下載)
相同原始來源
如果兩個 URL 具有相同的配置、主機和連接埠,則其原始來源相同 (RFC 6454)。
這兩個 URL 具有相同的原始來源:
https://example.com/foo.html
https://example.com/bar.html
這些 URL 的原始來源與前兩個 URL 不同:
https://example.net
:不同的網域https://www.example.com/foo.html
:不同的子網域http://example.com/foo.html
:不同的配置https://example.com:9000/foo.html
:不同的連接埠
啟用 CORS
有三種方式可以啟用 CORS:
- 在中介軟體中,使用具名原則或預設原則。
- 使用端點路由。
- 使用 [EnableCors] 屬性。
搭配具名原則使用 [EnableCors] 屬性能夠以最精細的程度來控制對於支援 CORS 之端點的限制。
警告
必須以正確的順序呼叫 UseCors。 如需詳細資訊,請參閱中介軟體順序。 例如,在使用 UseResponseCaching
時,必須先呼叫 UseCors
,再呼叫 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 擴充方法,並指定
_myAllowSpecificOrigins
CORS 原則。UseCors
會新增 CORS 中介軟體。 對UseCors
的呼叫必須放在UseRouting
之後、UseAuthorization
之前。 如需詳細資訊,請參閱中介軟體順序。 - 使用 Lambda 運算式呼叫 AddCors。 Lambda 會採用 CorsPolicyBuilder 物件。 本文稍後會說明組態選項,例如:
WithOrigins
。 - 為所有控制器端點啟用
_myAllowSpecificOrigins
CORS 原則。 請參閱端點路由以將 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
必須放在 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 端點不允許跨原始來源要求,因為未指定預設原則。
如果 CORS 是由端點路由透過 RequireCors
所啟用的,則 [DisableCors] 屬性無法停用 CORS。
如需與上述類似的測試程式碼指示,請參閱使用端點路由和 [HttpOptions] 測試 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.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 原則中設定的各種選項:
在 Program.cs
中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 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
中規定的標頭完全相符時,CORS 中介軟體原則才能與 WithHeaders
指定的特定標頭相符。
例如,假設某個應用程式的設定如下:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中介軟體會拒絕具有下列要求標頭的預檢要求,因為 Content-Language
(HeaderNames.ContentLanguage) 未在 WithHeaders
中列出:
Access-Control-Request-Headers: Cache-Control, Content-Language
應用程式會傳回 200 OK 回應,但不會將 CORS 標頭傳回。 因此,瀏覽器不會嘗試跨原始來源要求。
設定公開的回應標頭
根據預設,瀏覽器不會向應用程式公開所有回應標頭。 如需詳細資訊,請參閱 W3C 跨原始來源資源共用 (術語):簡單回應標頭。
預設可用的回應標頭為:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規格將此類標頭稱之為簡單回應標頭。 若要讓應用程式能夠使用其他標頭,請呼叫 WithExposedHeaders:
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 要求中進行特殊處理。 根據預設,瀏覽器不會傳送具有跨原始來源要求的認證。 認證包括 ookie 和 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
}
});
使用擷取 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 測試] 按鈕所發出預檢要求類似的範例回應。
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 的網頁瀏覽器。
- 由用戶端 (瀏覽器) 強制執行 CORS。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
- 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR 或擷取 API 要求 (其他方式將被禁止)。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
<script>
標籤來接收回應。 允許跨原始來源載入指令碼。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
CORS 規格引進了數個新的 HTTP 標頭,可啟用跨原始來源要求。 如果瀏覽器支援 CORS,則瀏覽器會自動為跨原始來源要求設定這些標頭。 不需要自訂 JavaScript 程式碼即可啟用 CORS。
以下範例演示了從 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 向端點提出要求,這些 HTTP 會因為使用 ERR_INVALID_REDIRECT on the CORS preflight request
的 UseHttpsRedirection 失敗而重新導向至 HTTPS。
API 專案可以拒絕 HTTP 要求,而不是使用 UseHttpsRedirection
來將要求重新導向至 HTTPS。
顯示 OPTIONS 要求
根據預設,Chrome 和 Edge 瀏覽器不會在 F12 工具的 [網路] 索引標籤上顯示 OPTIONS 要求。 若要在這些瀏覽器中顯示 OPTIONS 要求:
chrome://flags/#out-of-blink-cors
或edge://flags/#out-of-blink-cors
- 停用旗標。
- 重新啟動。
Firefox 預設會顯示 OPTIONS 要求。
IIS 中的 CORS
部署至 IIS 時,如果伺服器未設定為允許匿名存取,CORS 必須在 Windows 驗證之前執行。 若要支援此案例,必須為應用程式安裝並設定 IIS CORS 模組。
測試 CORS
範例下載包含用於測試 CORS 的程式碼。 請參閱如何下載。 此範例是新增了 Razor Pages 的 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();
警告
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 套件提供,並顯示路由資訊。
使用下列其中一種方法測試上述範例程式碼:
- 使用預設 URL
https://localhost:5001
執行dotnet run
指令以執行範例。 - 從 Visual Studio 執行範例,並將 URL
https://localhost:44398
的連接埠設定為 44398。
搭配 F12 工具使用瀏覽器:
選取 [值] 按鈕,然後檢閱 [網路] 索引標籤中的標頭。
選取 [PUT 測試] 按鈕。 如需顯示 OPTIONS 要求的指示,請參閱顯示 OPTIONS 要求。 PUT 測試會建立兩個要求:OPTIONS 預檢要求和 PUT 要求。
選取 [
GetValues2 [DisableCors]
] 按鈕以觸發失敗的 CORS 要求。 如檔中所述,回應會傳回 200 成功,但不會提出 CORS 要求。 選取 [主控台] 索引標籤以查看 CORS 錯誤。 視瀏覽器而定,會顯示類似下列的錯誤:CORS 原則已封鎖從來源
'https://cors3.azurewebsites.net'
中'https://cors1.azurewebsites.net/api/values/GetValues2'
擷取的存取權:要求的資源上沒有 '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.In 控制器下拉式清單,選取[預檢],然後[設定控制器]。 所有對 TodoItems2Controller
端點的 CORS 呼叫都成功。
其他資源
作者:Rick Anderson 與 Kirk Larkin
本文說明如何在 ASP.NET Core 應用程式中啟用 CORS。
瀏覽器安全性可防止網頁向提供網頁的不同網域提出要求。 這項限制稱為相同來源原則。 相同來源原則會阻止惡意網站從另一個網站讀取敏感性資料。 有時候,您可能想要允許其他網站對您的應用程式提出跨原始來源要求。 如需詳細資訊,請參閱 Mozilla CORS 文章。
跨原始來源資源分享 (CORS):
- 這是一個 W3C 標準,可讓伺服器放寬相同原始來源原則。
- 不是安全性功能,CORS 會放寬安全性。 允許 CORS 並不會增強 API 的安全性。 如需詳細資訊,請參閱 CORS 的運作方式。
- 允許伺服器明確允許某些跨原始來源要求,同時拒絕其他要求。
- 遠比舊版技術,例如 JSONP更加安全,更有彈性。
檢視或下載範例程式碼 \(英文\) (如何下載)
相同原始來源
如果兩個 URL 具有相同的配置、主機和連接埠,則其原始來源相同 (RFC 6454)。
這兩個 URL 具有相同的原始來源:
https://example.com/foo.html
https://example.com/bar.html
這些 URL 的原始來源與前兩個 URL 不同:
https://example.net
:不同的網域https://www.example.com/foo.html
:不同的子網域http://example.com/foo.html
:不同的配置https://example.com:9000/foo.html
:不同的連接埠
啟用 CORS
有三種方式可以啟用 CORS:
- 在中介軟體中,使用具名原則或預設原則。
- 使用端點路由。
- 使用 [EnableCors] 屬性。
搭配具名原則使用 [EnableCors] 屬性能夠以最精細的程度來控制對於支援 CORS 之端點的限制。
警告
必須以正確的順序呼叫 UseCors。 如需詳細資訊,請參閱中介軟體順序。 例如,在使用 UseResponseCaching
時,必須先呼叫 UseCors
,再呼叫 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 擴充方法,並指定
_myAllowSpecificOrigins
CORS 原則。UseCors
會新增 CORS 中介軟體。 對UseCors
的呼叫必須放在UseRouting
之後、UseAuthorization
之前。 如需詳細資訊,請參閱中介軟體順序。 - 使用 Lambda 運算式呼叫 AddCors。 Lambda 會採用 CorsPolicyBuilder 物件。 本文稍後會說明組態選項,例如:
WithOrigins
。 - 為所有控制器端點啟用
_myAllowSpecificOrigins
CORS 原則。 請參閱端點路由以將 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 端點不允許跨原始來源要求,因為未指定預設原則。
如果 CORS 是由端點路由透過 RequireCors
所啟用的,則 [DisableCors] 屬性無法停用 CORS。
如需與上述類似的測試程式碼指示,請參閱使用端點路由和 [HttpOptions] 測試 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 原則:
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
如果 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 原則中設定的各種選項:
在 Startup.ConfigureServices
中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 CORS 的運作方式一節,應該會很有幫助。
設定允許的原始來源
AllowAnyOrigin:允許來自所有原始來源、具有任何配置 (http
或 https
) 的 CORS 請求。 AllowAnyOrigin
不安全,因為任何網站都可以對應用程式提出跨原始來源要求。
注意
同時指定 AllowAnyOrigin
和 AllowCredentials
是一種不安全的組態,可能會導致跨網站偽造要求。 當應用程式設定了這兩種方法時,CORS 服務會傳回不正確的 CORS 回應。
AllowAnyOrigin
影響預檢要求和 Access-Control-Allow-Origin
標頭。 如需詳細資訊,請參閱<預檢要求>一節。
SetIsOriginAllowedToAllowWildcardSubdomains:將原則的 IsOriginAllowed 屬性設定為函式,後者的作用是在評估是否允許原始來源時允許原始來源比對已設定的萬用字元網域。
options.AddPolicy("MyAllowSubdomainPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
設定允許的 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-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規格將此類標頭稱之為簡單回應標頭。 若要讓應用程式能夠使用其他標頭,請呼叫 WithExposedHeaders:
options.AddPolicy("MyExposeResponseHeadersPolicy",
policy =>
{
policy.WithOrigins("https://*.example.com")
.WithExposedHeaders("x-custom-header");
});
跨原始來源要求中的認證
認證要求在 CORS 要求中進行特殊處理。 根據預設,瀏覽器不會傳送具有跨原始來源要求的認證。 認證包括 ookie 和 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
}
});
使用擷取 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-urlencoded
multipart/form-data
text/plain
用戶端要求所設定之要求標頭的規則會套用至應用程式透過對 XMLHttpRequest
物件呼叫 setRequestHeader
所設定的標頭。 CORS 規格將此類標頭稱之為作者要求標頭。 規則不適用於瀏覽器可以設定的標頭,例如:User-Agent
、Host
或 Content-Length
。
以下是與本文件的測試 CORS 區段中 [Put 測試] 按鈕所發出預檢要求類似的範例回應。
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 的網頁瀏覽器。
- 由用戶端 (瀏覽器) 強制執行 CORS。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
- 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR 或擷取 API 要求 (其他方式將被禁止)。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
<script>
標籤來接收回應。 允許跨原始來源載入指令碼。
- 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用
CORS 規格引進了數個新的 HTTP 標頭,可啟用跨原始來源要求。 如果瀏覽器支援 CORS,則瀏覽器會自動為跨原始來源要求設定這些標頭。 不需要自訂 JavaScript 程式碼即可啟用 CORS。
以下範例演示了從 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
標頭,跨原始來源要求會失敗。 具體而言,瀏覽器不允許要求。 即使伺服器傳回成功的回應,瀏覽器也不會將回應提供給用戶端應用程式。
顯示 OPTIONS 要求
根據預設,Chrome 和 Edge 瀏覽器不會在 F12 工具的 [網路] 索引標籤上顯示 OPTIONS 要求。 若要在這些瀏覽器中顯示 OPTIONS 要求:
chrome://flags/#out-of-blink-cors
或edge://flags/#out-of-blink-cors
- 停用旗標。
- 重新啟動。
Firefox 預設會顯示 OPTIONS 要求。
IIS 中的 CORS
部署至 IIS 時,如果伺服器未設定為允許匿名存取,CORS 必須在 Windows 驗證之前執行。 若要支援此案例,必須為應用程式安裝並設定 IIS CORS 模組。
測試 CORS
範例下載包含用於測試 CORS 的程式碼。 請參閱如何下載。 此範例是新增了 Razor Pages 的 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();
});
}
}
警告
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 套件提供,並顯示路由資訊。
使用下列其中一種方法測試上述範例程式碼:
- 使用預設 URL
https://localhost:5001
執行dotnet run
指令以執行範例。 - 從 Visual Studio 執行範例,並將 URL
https://localhost:44398
的連接埠設定為 44398。
搭配 F12 工具使用瀏覽器:
選取 [值] 按鈕,然後檢閱 [網路] 索引標籤中的標頭。
選取 [PUT 測試] 按鈕。 如需顯示 OPTIONS 要求的指示,請參閱顯示 OPTIONS 要求。 PUT 測試會建立兩個要求:OPTIONS 預檢要求和 PUT 要求。
選取 [
GetValues2 [DisableCors]
] 按鈕以觸發失敗的 CORS 要求。 如檔中所述,回應會傳回 200 成功,但不會提出 CORS 要求。 選取 [主控台] 索引標籤以查看 CORS 錯誤。 視瀏覽器而定,會顯示類似下列的錯誤:CORS 原則已封鎖從來源
'https://cors3.azurewebsites.net'
中'https://cors1.azurewebsites.net/api/values/GetValues2'
擷取的存取權:要求的資源上沒有 '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.In 控制器下拉式清單,選取[預檢],然後[設定控制器]。 所有對 TodoItems2Controller
端點的 CORS 呼叫都成功。