iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0

前言

之前公司專案,前後端分離架構,且設定在不同站台,發生過如下錯誤
https://ithelp.ithome.com.tw/upload/images/20241006/20133954WKBgPl1QLD.png
/images/emoticon/emoticon04.gif

然後才接觸到 CORS 這知識 /images/emoticon/emoticon06.gif
所以也把之前整理的筆記分享出來,因為它與這系列也息息相關 ~ /images/emoticon/emoticon08.gif

分享主軸

  • CORS 到底是什麼
  • CORS 詳細發生過程為何
  • 如何設定

CORS

什麼是 CORS?

(Cross-Origin Resource Sharing)是一種機制,允許瀏覽器從不同的來源(域名)請求資源,主要是用來保護瀏覽器,而不是用來保護伺服器

總之就是,Server 還是接的到 Request,且也會回傳 Response,但是 Javascript 是收不到任何資訊的,是瀏覽器刻意不讓我們接到,只要 HTTP 的動詞是 GET 或是 POST ,基本上都會發出去/與回來,但瀏覽器刻意不讓我們接到

是否發生 CORS

1. 前後端分離並部署在同一個域名下
比如後端是 ASP .NET CORE WEB API,且前端站台與後端 API 部署在同一個域名下,這種情況下不會有跨域問題

2. 前後端分離並部署在不同域名下
比如前端網站和後端 API 各自擁有獨立的域名(兩個不同的站台,不同 Domain),這種情況下會產生跨來源(跨域)請求,瀏覽器預設會阻擋這些請求

瀏覽器的行為

  1. 在完全不同網址情況下,瀏覽器就會觸發一個機制,先透過 OPTIONS 的 Request 的請求(這個請求被稱為「預檢請求」Preflight Request) ,打過去 API SERVER,這時候 API 那邊要先回幾個 header,如下圖範例
    如果伺服器沒有正確回應預檢請求中的 CORS Header,瀏覽器就會阻擋後續的實際請求
    https://ithelp.ithome.com.tw/upload/images/20241006/20133954XBX8ZuRjKw.png

總結 : 若此 OPTIONS 第一次請求成功後,拿到如上圖中幾種 Header 設定後,比如 Max-Age : 600,表示 600 秒內不會再次檢查

  1. 預檢請求為瀏覽器會先發送 OPTIONS 請求,伺服器需要回應此請求的 CORS header
    註記 : 如果預檢請求通過,瀏覽器才會發送實際的請求,並允許 JavaScript 獲取回應的資料

CORS 如下兩張圖

https://ithelp.ithome.com.tw/upload/images/20241006/20133954nMDOMmSYlc.png

https://ithelp.ithome.com.tw/upload/images/20241006/20133954Y5rrM0BrOu.png

CORS(Header)

伺服器需要在回應中包含以下 CORS Header,告知瀏覽器這個請求是被允許的

1. Access-Control-Allow-Methods
瀏覽器可以接受的 HTTP 方法(動詞),例如 GET、POST、PUT、DELETE 等
範例:Access-Control-Allow-Methods: GET, POST, PUT, DELETE

2. Access-Control-Allow-Headers
瀏覽器可以接受的自定義 header
範例:Access-Control-Allow-Headers: Content-Type, Authorization

3. Access-Control-Max-Age
瀏覽器可以緩存預檢請求的結果的時間(秒數),在這段時間內不會再次發送預檢請求
範例:Access-Control-Max-Age: 1728000(表示 20000 分鐘)

4. Access-Control-Allow-Origin
允許發送請求的來源(域名)
範例:Access-Control-Allow-Origin: https://example.com

設定

三種方式可以啟用 CORS ( 以 ASP .NET Core 8 為例 )

1. 在中介軟體中,使用命名原則或預設原則

使用命名原則

var builder = WebApplication.CreateBuilder(args);

// 定義 CORS 原則
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                             "http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
                      });
});


var app = builder.Build();

app.UseRouting();

// 啟用預設的 CORS 原則
app.UseCors();

app.MapControllers();

...

使用預設原則

// 定義預設的 CORS 原則
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins("http://example.com",
                           "http://www.contoso.com")
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

....

app.UseRouting();

// 啟用預設的 CORS 原則
app.UseCors(MyAllowSpecificOrigins);

app.MapControllers();

....

2. 端點路由

// 定義 CORS 原則
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

....

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);
});
...

或是直接設定

app.MapControllers().RequireCors(MyAllowSpecificOrigins) 

//這句其實與下面這句是一樣意思
endpoints.MapControllers().RequireCors(MyAllowSpecificOrigins);

補充解說

  • 特定路由指定時可以這樣設定 :endpoints.MapGet("/echo", ...):這個端點使用 RequireCors(MyAllowSpecificOrigins) 來應用特定的 CORS 原則

  • 沒特定路由需要設定,就可以使用 : endpoints.MapControllers().RequireCors(MyAllowSpecificOrigins):這會將 MyAllowSpecificOrigins 原則應用到所有控制器

補充 : 使用端點路由時,CORS 中介軟體必須設定為在呼叫 UseRouting 和 UseEndpoints 之間執行

3. 使用 [EnableCors] 屬性

  • 除了全域設定外,還可以針對特定控制器與方法做設定
// 定義 CORS 原則
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                             "http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
                      });
});

app.UseRouting();

// 啟用 CORS 中介軟體,必須在 UseRouting 和 UseEndpoints 之間
app.UseCors();
...

    [EnableCors("_myAllowSpecificOrigins")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

4. 停用[EnableCors] 屬性 : [DisableCors]

     // 這個方法會停用 CORS
    [DisableCors]
    [HttpGet("disable-cors")]
    public IActionResult DisableCors()
    {
        return Ok("CORS is disabled for this method.");
    }

補充 : 如果 CORS 是通過端點路由配置啟用,則 [DisableCors] 屬性無法停用 CORS

今日結語

今天這重點也是很重要,本篇文章先針對初步介紹,它還可以更詳細設定,學習這知識,大大可以知道整個應用程式與瀏覽器間的行為模式與原理,使自己對於開發上更可以了解,當碰到此錯誤,就可以知道為什麼!不會鬼打牆!

參考文章

中文官方文件
https://learn.microsoft.com/zh-tw/aspnet/core/security/cors?view=aspnetcore-8.0

英文官方文件
https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-8.0


上一篇
Day 22 User Secrets : ASP .NET Core 內建好用的設定檔案
下一篇
Day 24 Exception : 例外處理
系列文
靠近 ASP .NET Core 一點點27
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言