.NET Core 允許跨域的兩種方式實現(IIS 設定、C# 程式碼實現)

2023-06-28 21:01:30

〇、前言

當把開發好的 WebApi 介面,部署到 Windows 伺服器 IIS 後,postman 可以直接存取到介面並正確返回,這並不意味著任務完成,畢竟介面嘛是要有互動的,最常見的問題莫過於跨域了。

若前端檔案是在當前介面檔案下的 wwwroot 資料夾下,那麼介面的存取就沒問題,因為是同協定(http、https)、同地址(域名)、同埠,不存在跨域問題

但是,若前端和介面不是部署在一起的,那麼一般都會存在跨域問題,本文將通過兩種方式介紹如何使介面允許跨域請求。

一、IIS 設定實現

1、生效範圍

如下圖:

1 位置為 IIS 根目錄,在此屬性中設定「HTTP響應檔頭」時,作用域為「網站」下級目錄中的全部應用。若後面修改了單個應用的 Headers,當更新應用檔案後,修改會被還原。

2 位置是指定某一網站,在此屬性中設定「HTTP響應檔頭」時,作用域為當前應用,不對其他同級應用有影響。

  

2、常用的設定項共有四個

HTTP 響應檔頭 是否必含 解釋
Access-Control-Allow-Origin * 或 http://IP:Port 允許跨域請求的地址,* 代表允許全部,若指定地址則僅支援填入一個
Access-Control-Allow-Headers Content-Type 當介面僅提供 Get 請求時,可省略;另外使用者端新增的自定義請求頭,需再次進行允許設定
Access-Control-Allow-Methods POST, GET, OPTIONS, PUT, DELETE, UPDATE 此處列出了全部常用的方法名,可根據需要可適當刪除個別值
Access-Control-Allow-Credentials 預設為 false,可設定為 true 允許使用者端攜帶驗證資訊,例如 cookie 之類的。為 true 時,不允許 Origin 設定為「*」

二、C# 程式碼實現

1、設定範例

主要是通過在 Startup.cs 檔案中的 ConfigureServices() 方法新增跨域服務策略(services.AddCors()),然後在 Configure() 方法中將跨域策略加入到 HTTP 請求管道(HTTP request pipeline)中。

先列舉一個範例,.Net 5.0 設定相容預檢請求,如下程式碼:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    // 新增跨域策略
    services.AddCors(options =>
    {
        // 設定預設策略和中介軟體:options.AddDefaultPolicy(policy =>{policy.WithOrigins("");});app.UseCors(); // 將自動應用於所有控制器終結點
        options.AddPolicy("CorsPolicyName0519", policy =>
        {
            policy
            //.AllowAnyOrigin() // AllowAnyOrigin 允許任何源地址的存取
            .WithOrigins("http://IP:Port") // 僅允許一個地址存取
            //.WithOrigins(new string[]{"http://IP1:Port1","http://IP2:Port2","http://IP3:Port3"}) // 支援同時允許多個指定地址的存取
            //.AllowAnyHeader() // 允許任何的Header頭部標題
            .WithHeaders("Account", "ClientType", "OrgId", "Token", "Department", "EntAuthVebr") // 自定義請求頭
            //.AllowAnyMethod() // 允許任何方法
            .WithMethods(HttpMethods.Options, HttpMethods.Get, HttpMethods.Post, HttpMethods.Put, HttpMethods.Delete) // 允許的謂詞方法
            //.AllowCredentials() // 允許跨源請求傳送憑據 允許時 Origin 不允許為「*」
            .SetPreflightMaxAge(TimeSpan.FromHours(24)); // 設定預檢請求的最大快取時間
        });
    });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...
    app.UseCors("CorsPolicyName0519"); // 新增 CORS 中介軟體,允許跨域存取
    // ...
}

跨域請求策略可以同時設定多個。

使用 [EnableCors] 屬性可以有針對性的啟用同一個 CORS。也可以對需要 CORS 的終結點設定指定的策略名稱,來實現最佳控制。

  • [EnableCors] 指定預設策略。
  • [EnableCors("{Policy String}")] 指定命名策略。

[EnableCors] 屬性可應用於:控制器、控制器操作方法、Razor Page PageModel。

將 [EnableCors] 屬性應用於控制器、操作方法或頁面模型,並將中介軟體加入到管道來啟用 CORS 時, 將這兩種策略將同時生效。

與 [EnableCors] 相反的,[DisableCors] 屬性標識禁用跨域策略。

通常,UseStaticFiles 在 之前 UseCors呼叫 。 使用 JavaScript 跨站點檢索靜態檔案的應用必須在 UseStaticFiles 之前呼叫 UseCors。

2、關於 設定允許的傳送請求的源地址 WithOrigins()

.AllowAnyOrigin:允許具有任何協定(http 或 https)的所有源的 CORS 請求。也就是說任何網站都可以嚮應用發出跨域請求,會導致跨網站請求偽造,因此並不安全。

.WithOrigins("http://IP1:Port1","http://IP2:Port2"):允許同時設定多個指定地址。(引數型別實際為:new string[]{ })

但是要設定具體的請求地址比較多時,全部通過 string[] 列出的話很不優雅,此時就需要通過萬用字元域來達到設定多地址的目的。

例如,當需求為允許多個地址(例如:*.example.com、https://*.example.net 同一字尾的多個域名萬用字元)時,就可以用到如下設定:

  SetIsOriginAllowedToAllowWildcardSubdomains:將策略的 IsOriginAllowed 屬性設定為一個函數,當計算是否允許源時,此函數允許源匹配已設定的萬用字元域。

services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy
                .WithOrigins("https://*.example.com","https://*.example.net") // 等效於:new string[]{"地址1","地址2"}
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

 3、關於 設定允許的 HTTP 方法 WithMethods()

這個就沒啥好說的了,需要那種就設定進去好了。

常用的就三種:Get、Post、Options。另外不常用的有六種:Put、Delete、Patch、Trace、Connect、Head。詳見:HTTP 請求方法

4、關於設定允許的請求頭 WithHeaders()

 .AllowAnyHeader():允許任何名稱的 Header 屬性。這種情況下,很容易出現非預設的請求頭,導致觸發預檢請求 Options,影響系統效能,下文章節會著重介紹。

 .WithHeaders(HeaderNames.ContentType, HeaderNames.UserAgent):指定允許多個請求頭。(引數型別實際為:new string[]{ })

當用戶端需要新增指定的請求頭,需要在 WithHeaders() 方法中全部設定上。

5、關於設定允許的響應頭 WithExposedHeaders()

預設情況下,瀏覽器不會嚮應用公開所有響應頭。預設可用的響應頭包括:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。

.WithExposedHeaders(HeaderNames.Server,HeaderNames.Status):允許同時設定多個響應頭。(引數型別實際為:new string[]{ })

6、設定允許跨源域請求傳送憑據 AllowCredentials()

憑據需要在 CORS 請求中進行特殊處理。 預設情況下,瀏覽器不會使用跨源域請求傳送憑據。 憑據包括 cookie 和 HTTP 身份驗證方案。 要使用跨源請求傳送憑據,使用者端必須將 Credentials 設定為 true,預設情況下為 false。

.AllowCredentials():允許跨源請求傳送憑據。

HTTP 響應包含一個 Access-Control-Allow-Credentials 頭,它告訴瀏覽器伺服器允許跨源請求的憑據。

如果瀏覽器傳送憑據,但響應不包含有效的 Access-Control-Allow-Credentials 頭,則瀏覽器不會嚮應用公開響應,而且跨源請求會失敗。

允許跨源憑據會帶來安全風險。另一個域中的網站可以在使用者不知情的情況下代表使用者將登入使用者的憑據傳送到應用。

CORS 規範還指出,如果存在 Access-Control-Allow-Credentials 頭,則將源 Origins 設定為「*」(所有源)是無效的,如下圖報錯提示:

  

參考:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-6.0

  https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-6.0

 三、關於預檢請求 Options

1、什麼是預檢請求?

「傳送非簡單跨域請求前的預檢請求」,若該請求未正常返回,瀏覽器會阻止後續的請求傳送。

注:Chrome 和 Microsoft Edge 瀏覽器不會在 F12 工具的 Network 索引標籤上顯示 OPTIONS 請求,需要額外設定,開啟地址:chrome://flags/#out-of-blink-cors 或 edge://flags/#out-of-blink-cors,禁用,重啟生效;Firefox 瀏覽器預設顯示 OPTIONS 請求。

如下圖,是一個預檢請求的 headers 資訊:

  

 2、什麼情況下會觸發預檢請求

預檢請求(Options)屬於實際請求(Get、Post 等)之外的操作,僅在部分情況下觸發。

想達到不觸發 Options 方法的目的,需同時滿足下面三個條件:

  • 請求方法為 GET、POST 或 HEAD
  • 應用不會設定 Content-Type、Content-Language、Accept、Accept-Language 或 Last-Event-ID 以外的請求頭
  • Content-Type 頭(如果已設定)具有以下三個值之一:application/x-www-form-urlencoded、multipart/form-data、text/plain

預檢請求可能包含以下 Headers:

  • Access-Control-Request-Method/Methods:將用於實際請求的 HTTP 方法。
  • Access-Control-Request-Headers:應用在實際請求上設定的請求頭的列表。 如前文所述,這不包含瀏覽器設定的檔頭,如 User-Agent、Host、Content-Length 等。

如果預檢請求被拒絕,應用將返回 200 OK 響應,但不會設定 CORS 頭,瀏覽器後續也就不會嘗試跨源請求。

3、預檢請求的 [HttpOptions] 屬性

當使用適當的策略啟用 CORS 時,ASP.NET Core 通常會自動響應 CORS 預檢請求。 但在某些情況下, 例如通過終結點路由使用 CORS,是不會自動響應的。

以下是官網給出的範例,分別是帶引數的 Options 請求和不帶引數兩種:

詳見官網:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-6.0

[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();
    }
}

4、設定預檢過期時間 SetPreflightMaxAge()

Access-Control-Max-Age 頭指定對預檢請求的響應可以快取多長時間。

此方法的目的是在第一次預檢請求成功後,將預檢結果快取一段時間,從而避免重複的預檢請求,提升應用效能。

程式碼設定跨域策略時,可通過 .SetPreflightMaxAge() 來實現,如下程式碼:

// 新增跨域策略
services.AddCors(options =>
{
    options.AddPolicy("CorsPolicyName007", policy =>
    {
        policy
        .WithOrigins("http://127.0.0.1:7000" , "http://127.0.0.1:8000" )
        .SetPreflightMaxAge(TimeSpan.FromHours(24)) // 設定預檢請求的最大快取時間
        ;
    });
});

參考:https://learn.microsoft.com/zh-cn/aspnet/core/security/cors?view=aspnetcore-6.0