共用方式為


在 ASP.NET Core 中啟用跨原始來源要求 (CORS)

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本

警告

不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本

重要

這些發行前產品的相關資訊在產品正式發行前可能會有大幅修改。 Microsoft 對此處提供的資訊,不做任何明確或隱含的瑕疵擔保。

如需目前版本,請參閱本文的 .NET 8 版本

作者:Rick AndersonKirk 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] 屬性能夠以最精細的程度來控制對於支援 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 中介軟體必須設定為在呼叫 UseRoutingUseEndpoints 之間執行。

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 原則選項

本節描述可在 CORS 原則中設定的各種選項:

Program.cs 中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 CORS 的運作方式一節,應該會很有幫助。

設定允許的原始來源

AllowAnyOrigin:允許來自所有原始來源、具有任何配置 (httphttps) 的 CORS 請求。 AllowAnyOrigin 不安全,因為任何網站都可以對應用程式提出跨原始來源要求。

注意

同時指定 AllowAnyOriginAllowCredentials 是一種不安全的組態,可能會導致跨網站偽造要求。 當應用程式設定了這兩種方法時,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 方法

AllowAnyMethod

  • 允許任何 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。
  • 應用程式不會設定 AcceptAccept-LanguageContent-LanguageContent-TypeLast-Event-ID 以外的要求標頭。
  • Content-Type 標頭 (如果已設定) 具有下列其中一個值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

用戶端要求所設定之要求標頭的規則會套用至應用程式透過對 XMLHttpRequest 物件呼叫 setRequestHeader 所設定的標頭。 CORS 規格將此類標頭稱之為作者要求標頭。 規則不適用於瀏覽器可以設定的標頭,例如:User-AgentHostContent-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 方法。 其中可能包含下列標頭:

如果預檢要求遭到拒絕,應用程式會傳迴 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:則要至少包含 AcceptContent-TypeOrigin,以及您想要支援的任何自訂標頭。

自動預檢要求程式碼

透過下列任一方法來套用 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。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
  • 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR擷取 API 要求 (其他方式將被禁止)。
    • 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用 <script> 標籤來接收回應。 允許跨原始來源載入指令碼。

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 requestUseHttpsRedirection 失敗而重新導向至 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 的端點,例如:curlFiddler。 使用工具時,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 AndersonKirk 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] 屬性能夠以最精細的程度來控制對於支援 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 中介軟體必須設定為在呼叫 UseRoutingUseEndpoints 之間執行。

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 原則選項

本節描述可在 CORS 原則中設定的各種選項:

Program.cs 中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 CORS 的運作方式一節,應該會很有幫助。

設定允許的原始來源

AllowAnyOrigin:允許來自所有原始來源、具有任何配置 (httphttps) 的 CORS 請求。 AllowAnyOrigin 不安全,因為任何網站都可以對應用程式提出跨原始來源要求。

注意

同時指定 AllowAnyOriginAllowCredentials 是一種不安全的組態,可能會導致跨網站偽造要求。 當應用程式設定了這兩種方法時,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 方法

AllowAnyMethod

  • 允許任何 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。
  • 應用程式不會設定 AcceptAccept-LanguageContent-LanguageContent-TypeLast-Event-ID 以外的要求標頭。
  • Content-Type 標頭 (如果已設定) 具有下列其中一個值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

用戶端要求所設定之要求標頭的規則會套用至應用程式透過對 XMLHttpRequest 物件呼叫 setRequestHeader 所設定的標頭。 CORS 規格將此類標頭稱之為作者要求標頭。 規則不適用於瀏覽器可以設定的標頭,例如:User-AgentHostContent-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 方法。 其中可能包含下列標頭:

如果預檢要求遭到拒絕,應用程式會傳迴 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:則要至少包含 AcceptContent-TypeOrigin,以及您想要支援的任何自訂標頭。

自動預檢要求程式碼

透過下列任一方法來套用 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。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
  • 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR擷取 API 要求 (其他方式將被禁止)。
    • 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用 <script> 標籤來接收回應。 允許跨原始來源載入指令碼。

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 requestUseHttpsRedirection 失敗而重新導向至 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 的端點,例如:curlFiddler。 使用工具時,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 AndersonKirk 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] 屬性能夠以最精細的程度來控制對於支援 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 中介軟體必須設定為在呼叫 UseRoutingUseEndpoints 之間執行。

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 原則選項

本節描述可在 CORS 原則中設定的各種選項:

Program.cs 中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 CORS 的運作方式一節,應該會很有幫助。

設定允許的原始來源

AllowAnyOrigin:允許來自所有原始來源、具有任何配置 (httphttps) 的 CORS 請求。 AllowAnyOrigin 不安全,因為任何網站都可以對應用程式提出跨原始來源要求。

注意

同時指定 AllowAnyOriginAllowCredentials 是一種不安全的組態,可能會導致跨網站偽造要求。 當應用程式設定了這兩種方法時,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 方法

AllowAnyMethod

  • 允許任何 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。
  • 應用程式不會設定 AcceptAccept-LanguageContent-LanguageContent-TypeLast-Event-ID 以外的要求標頭。
  • Content-Type 標頭 (如果已設定) 具有下列其中一個值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

用戶端要求所設定之要求標頭的規則會套用至應用程式透過對 XMLHttpRequest 物件呼叫 setRequestHeader 所設定的標頭。 CORS 規格將此類標頭稱之為作者要求標頭。 規則不適用於瀏覽器可以設定的標頭,例如:User-AgentHostContent-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 方法。 其中可能包含下列標頭:

如果預檢要求遭到拒絕,應用程式會傳迴 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:則要至少包含 AcceptContent-TypeOrigin,以及您想要支援的任何自訂標頭。

自動預檢要求程式碼

透過下列任一方法來套用 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。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
  • 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR擷取 API 要求 (其他方式將被禁止)。
    • 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用 <script> 標籤來接收回應。 允許跨原始來源載入指令碼。

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 requestUseHttpsRedirection 失敗而重新導向至 HTTPS。

API 專案可以拒絕 HTTP 要求,而不是使用 UseHttpsRedirection 來將要求重新導向至 HTTPS。

顯示 OPTIONS 要求

根據預設,Chrome 和 Edge 瀏覽器不會在 F12 工具的 [網路] 索引標籤上顯示 OPTIONS 要求。 若要在這些瀏覽器中顯示 OPTIONS 要求:

  • chrome://flags/#out-of-blink-corsedge://flags/#out-of-blink-cors
  • 停用旗標。
  • 重新啟動。

Firefox 預設會顯示 OPTIONS 要求。

IIS 中的 CORS

部署至 IIS 時,如果伺服器未設定為允許匿名存取,CORS 必須在 Windows 驗證之前執行。 若要支援此案例,必須為應用程式安裝並設定 IIS CORS 模組

測試 CORS

範例下載包含用於測試 CORS 的程式碼。 請參閱如何下載。 此範例是新增了 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 的端點,例如:curlFiddler。 使用工具時,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 AndersonKirk 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] 屬性能夠以最精細的程度來控制對於支援 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 中介軟體必須設定為在呼叫 UseRoutingUseEndpoints 之間執行。

如需與上述程式碼類似的程式碼測試的指示,請參閱測試 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 原則選項

本節描述可在 CORS 原則中設定的各種選項:

Startup.ConfigureServices 中會呼叫 AddPolicy。 對於某些選項,使用者應先閱讀 CORS 的運作方式一節,應該會很有幫助。

設定允許的原始來源

AllowAnyOrigin:允許來自所有原始來源、具有任何配置 (httphttps) 的 CORS 請求。 AllowAnyOrigin 不安全,因為任何網站都可以對應用程式提出跨原始來源要求。

注意

同時指定 AllowAnyOriginAllowCredentials 是一種不安全的組態,可能會導致跨網站偽造要求。 當應用程式設定了這兩種方法時,CORS 服務會傳回不正確的 CORS 回應。

AllowAnyOrigin 影響預檢要求和 Access-Control-Allow-Origin 標頭。 如需詳細資訊,請參閱<預檢要求>一節。

SetIsOriginAllowedToAllowWildcardSubdomains:將原則的 IsOriginAllowed 屬性設定為函式,後者的作用是在評估是否允許原始來源時允許原始來源比對已設定的萬用字元網域。

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

設定允許的 HTTP 方法

AllowAnyMethod

  • 允許任何 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。
  • 應用程式不會設定 AcceptAccept-LanguageContent-LanguageContent-TypeLast-Event-ID 以外的要求標頭。
  • Content-Type 標頭 (如果已設定) 具有下列其中一個值:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

用戶端要求所設定之要求標頭的規則會套用至應用程式透過對 XMLHttpRequest 物件呼叫 setRequestHeader 所設定的標頭。 CORS 規格將此類標頭稱之為作者要求標頭。 規則不適用於瀏覽器可以設定的標頭,例如:User-AgentHostContent-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 方法。 其中可能包含下列標頭:

如果預檢要求遭到拒絕,應用程式會傳迴 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:則要至少包含 AcceptContent-TypeOrigin,以及您想要支援的任何自訂標頭。

自動預檢要求程式碼

透過下列任一方法來套用 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。 伺服器只是執行要求並傳迴響應,而傳回錯誤並封鎖回應的則是用戶端。 例如,下列任何工具都會顯示伺服器回應:
  • 伺服器以這種方式來允許瀏覽器執行跨原始來源 XHR擷取 API 要求 (其他方式將被禁止)。
    • 沒有 CORS 的瀏覽器無法執行跨原始來源要求。 在 CORS 問世以前,一般會使用 JSONP 來規避這項限制。 JSONP 不會使用到 XHR,而是使用 <script> 標籤來接收回應。 允許跨原始來源載入指令碼。

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-corsedge://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 的端點,例如:curlFiddler。 使用工具時,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 呼叫都成功。

其他資源