深入剖析.NETCORE中CORS(跨站資源共用)

来源:https://www.cnblogs.com/viter/archive/2020/07/24/13367650.html
-Advertisement-
Play Games

通過對 Microsoft.AspNetCore.Cors 的內部實現的剖析,我們瞭解到,其實現 CORS 的原理非常簡單,結構清晰,就算不用系統自帶的 CORS 組件,自行實現一個 CORS 策略,也是非常容易的。 ...


前言

由於現代互聯網的飛速發展,我們在開發現代 Web 應用程式中,經常需要考慮多種類型的客戶端訪問服務的情況;而這種情況放在15年前幾乎是不可想象的,在那個時代,我們更多的是考慮怎麼把網頁快速友好的嵌套到服務代碼中,經過伺服器渲染後輸出HTML到客戶端,沒有 iOS,沒有 Android,沒有 UWP。更多的考慮是 防止 XSS,在當時的環境下,XSS一度成為各個站長的噩夢,甚至網站開發的基本要求都要加上:必須懂防 XSS 攻擊。

CORS 定義

言歸正傳,CORS(Cross-Origin Resource Sharing)是由 W3C 指定的標準,其目的是幫助在各個站點間的資源共用。CORS 不是一項安全標準,啟用 CORS 實際上是讓站點放寬了安全標準;通過配置 CORS,可以允許配置中的請求源執行允許/拒絕的動作。

在 .NETCore 中啟用 CORS

在 .NETCore 中,已經為我們集成好 CORS 組件 Microsoft.AspNetCore.Cors,在需要的時候引入該組件即可,Microsoft.AspNetCore.Cors 的設計非常的簡潔,包括兩大部分的內容,看圖:

從上圖中我們可以看出,左邊是入口,是我們常見的 AddCors/UseCors,右邊是 CORS 的核心配置和驗證,配置對象是 CorsPolicyBuilder 和 CorsPolicy,驗證入口為 CorsService,中間件 CorsMiddleware 提供了攔截驗證入口。

CorsService 是整個 CORS 的核心實現,客戶端的請求流經中間件或者AOP組件後,他們在內部調用 CorsService 的相關驗證方法,在 CorsService 內部使用配置好的 PolicyName 拉去相關策略進行請求驗證,最終返回驗證結果到客戶端。

Microsoft.AspNetCore.Mvc.Cors

通常情況下,我們會在 Startup 類中的 ConfigureServices(IServiceCollection services) 方法內部調用 AddCors() 來啟用 CROS 策略,但是,該 AddCors() 並不是上圖中 CorsServiceCollectionExtensions 中的 AddCors 擴展方法。

實際上,在 ConfigureServices 中調用的 AddCors 是處於程式集 Microsoft.AspNetCore.Mvc.Cors ;在 Microsoft.AspNetCore.Mvc.Cors 內部的擴展方法 AddCors() 中,以 AOP 方式定義了對 EnableCorsAttribute/DisableCorsAttributeAttribute 的攔截檢查。

具體做法是在程式集 Microsoft.AspNetCore.Mvc.Cors 內部,定義了類 CorsApplicationModelProvider ,當我們調用 AddCors 擴展方法的時候,將進一步調用 CorsApplicationModelProvider.OnProvidersExecuting(ApplicationModelProviderContext context) 方法,從而執行檢查 EnableCorsAttribute/DisableCorsAttributeAttribute 策略。

所以,我們在 ConfigureServices 中調用的 AddCore,其實是在該程式集內部定義的類: MvcCorsMvcCoreBuilderExtensions 的擴展方法,我們看 MvcCorsMvcCoreBuilderExtensions 的定義

public static class MvcCorsMvcCoreBuilderExtensions
{
    public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder)
    {
       ...
       AddCorsServices(builder.Services);
       ...
    }

    public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder,Action<CorsOptions> setupAction)
    {
      ...
      AddCorsServices(builder.Services);
      ...
    }

    public static IMvcCoreBuilder ConfigureCors(this IMvcCoreBuilder builder,Action<CorsOptions> setupAction)
    {
      ...
    }

    // Internal for testing.
    internal static void AddCorsServices(IServiceCollection services)
    {
        services.AddCors();

        services.TryAddEnumerable(
            ServiceDescriptor.Transient<IApplicationModelProvider, CorsApplicationModelProvider>());
        services.TryAddTransient<CorsAuthorizationFilter, CorsAuthorizationFilter>();
    }
}

重點就在上面的 AddCorsServices(IServiceCollection services) 方法中, 在方法中調用了 CORS 的擴展方法 AddCors()。

那麼我們就要問, CorsApplicationModelProvider 是在什麼時候被初始化的呢?
答案是在 startup 中 ConfigureServices(IServiceCollection services) 方法內調用 services.AddControllers() 的時候。在AddControllers() 方法內部,調用了 AddControllersCore 方法

private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
{
    // This method excludes all of the view-related services by default.
    return services
        .AddMvcCore()
        .AddApiExplorer()
        .AddAuthorization()
        .AddCors()
        .AddDataAnnotations()
        .AddFormatterMappings();
}

理解了 CORS 的執行過程,下麵我們就可以開始瞭解應該怎麼在 .NETCore 中使用 CORS 的策略了

CORS 啟用的三種方式

在 .NETCore 中,可以通過以下三種方式啟用 CORS

1、使用預設策略/命名策略的中間件的方式
2、終結點路由 + 命名策略
3、命名策略 + EnableCorsAttribute

通過上面的三種方式,可以靈活在程式中控制請求源的走向,但是,殘酷的事實告訴我們,一般情況下,我們都是會對全站進行 CORS。所以,現實情況就是在大部分的 Web 應用程式中, CORS 已然成為皇帝的新裝,甚至有點累贅。

CorsPolicyBuilder(CORS策略)

通過上面的 CORS 思維導圖,我們已經大概瞭解了 CORS 的整個結構。由上圖我們知道,CorsPolicyBuilder 位於命名空間 Microsoft.AspNetCore.Cors.Infrastructure 中。
在內部提供了兩種基礎控制策略:全開/半開。這兩種策略都提供了基本的方法供開發者直接調用,非常的貼心。

全開

public CorsPolicyBuilder AllowAnyHeader();
public CorsPolicyBuilder AllowAnyMethod();
public CorsPolicyBuilder AllowAnyOrigin();
public CorsPolicyBuilder AllowCredentials();

半開

public CorsPolicyBuilder DisallowCredentials();
public CorsPolicyBuilder WithHeaders(params string[] headers);
public CorsPolicyBuilder WithMethods(params string[] methods);
public CorsPolicyBuilder WithOrigins(params string[] origins);

上面的策略定義從字面理解就可以知道其用途,實際上呢,他們的實現原理也是非常的簡單。在 CorsPolicyBuilder 內部維護著一個 CorsPolicy 對象,當你使用全開/半開方式配置策略的時候,builder 會將配置寫入內部 CorsPolicy 中存儲備用。

比如半開 WithOrigins(params string[] origins);,通過迭代器將配置的源寫入 _policy.Origins 中。

    public CorsPolicyBuilder WithOrigins(params string[] origins)
    {
        foreach (var origin in origins)
        {
            var normalizedOrigin = GetNormalizedOrigin(origin);
            _policy.Origins.Add(normalizedOrigin);
        }

        return this;
    }

開始使用

在理解了配置的過程後,我們就可以進入真正的使用環節了,通過上面的學習我們知道,啟用 CORS 有三種方式,咱們一步一步來。

使用預設策略/命名策略的中間件的方式

所謂的命名策略就是給你的策略起個名字,預設策略就是沒有名字,所有的入口都使用同一個策略,下麵的代碼演示了命名策略

private readonly string CORS_ALLOW_ORGINS = "cors_allow_orgins";

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(CORS_ALLOW_ORGINS, policy =>
        {
            policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
        });
    });
    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
    });
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseRouting();
    app.UseCors(CORS_ALLOW_ORGINS);
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

上面的代碼演示瞭如何在站點中全局終結點啟用 CORS,首先聲明瞭命名策略 cors_allow_orgins ,然後將其用 AddCors() 添加到 CORS 中,最後使用 UseCors() 啟用該命名策略,需要註意的是,AddCors() 和 UseCors() 必須成對出現,並且要使用同一個命名策略。

終結點路由 + 命名策略

.NETCore 支持通過對單個路由設置 CORS 命名策略,從而可以實現在一個系統中,對不同的業務提供個性化的支持。終結點路由 + 命名策略的配置和上面的命名策略基本相同,僅僅是在配置路由的時候,只需要對某個路由增加 RequireCors 的配置即可

private readonly string CORS_ALLOW_ORGINS = "cors_allow_orgins";
public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(CORS_ALLOW_ORGINS, policy =>
        {
            policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
        });
    });
    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
    });
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute("weatherforecast", "{controller=WeatherForecast}/{action=Get}").RequireCors(CORS_ALLOW_ORGINS);
        // endpoints.MapControllers();
    });
}

上面的代碼,指定了路由 weatherforecast 需要執行 CORS 策略 CORS_ALLOW_ORGINS。通過調用 RequireCors() 方法,傳入策略名稱,完成 CORS 的配置。RequireCors 方法是在程式集 Microsoft.AspNetCore.Cors 內部的擴展方法,具體是怎麼啟用策略的呢,其實就是在內部給指定的終結點路由增加了 EnableCorsAttribute ,這就是下麵要說到的第三種啟用 CORS 的方式。

來看看 RequireCors() 內部的代碼

public static TBuilder RequireCors<TBuilder>(this TBuilder builder, string policyName) where TBuilder : IEndpointConventionBuilder
{
    if (builder == null)
    {
        throw new ArgumentNullException(nameof(builder));
    }
    builder.Add(endpointBuilder =>
    {
        endpointBuilder.Metadata.Add(new EnableCorsAttribute(policyName));
    });
    return builder;
}

命名策略 + EnableCorsAttribute

最後一種啟用 CORS 的方式是使用 EnableCorsAttribute 特性標記,和 RequireCors 方法內部的實現不同的是,這裡說的 EnableCorsAttribute 是顯式的指定到控制器上,在應用 EnableCorsAttribute 的時候,你可以應用到根控制器或者子控制器上,如果是對根控制器進行標記,被標記的根控制器和他的所有子控制器都將受指定 CORS 策略的影響;反之,如果只是對子控制器進行標記,CORS 策略也只對當前控制器產生影響。

CORS 的初始化

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy("controller_cors", policy =>
        {
            policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
        });
        options.AddPolicy("action_cors", policy =>
        {
            policy.WithOrigins("http://localhost:5500", "http://localhost:8099");
        });
    });
    services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new StringJsonConverter());
    });
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    app.UseRouting();
    app.UseCors();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

在上面的代碼中,因為 EnableCorsAttribute 可以應用到類和屬性上,所以我們定義了兩個 CORS 策略,分別是 controller_cors 和 action_cors。接下來將這兩種策略應用到 WeatherForecastController 上。

應用 EnableCorsAttribute 特性標記

[ApiController]
[Route("[controller]")]
[EnableCors("controller_cors")]
public class WeatherForecastController : ControllerBase
{
    [EnableCors("action_cors")]
    [HttpPost]
    public string Users()
    {
        return "Users";
    }

    [DisableCors]
    [HttpGet]
    public string List()
    {
        return "List";
    }

    [HttpGet]
    public string Index()
    {
        return "Index";
    }
}

在上面的 WeatherForecastController 控制器中,我們將 controller_cors 標記到控制器上,將 action_cors 標記到 Action 名稱為 Users 上面,同時,還對 List 應用了 DisableCors ,表示對 List 禁用 CORS 的策略,所以我們知道,在 CORS 中,有 AddCors/UseCors,也有 EnableCors/DisableCors ,都是成對出現的。

其它策略

我們還記得,在 .NETCore 中,一共有 4 種策略,分別是:Header、Method、Origin、Credentials,但是本文僅演示了 WithOrigins 這一種方式,相信通過這一種方式的演示,對大家在啟用其它策略的時候,其思想也是一致的,所謂的標頭、請求方式、憑據 等等,其基本法是不變的。

通過對 Microsoft.AspNetCore.Cors 的內部實現的剖析,我們瞭解到,其實現 CORS 的原理非常簡單,結構清晰,就算不用系統自帶的 CORS 組件,自行實現一個 CORS 策略,也是非常容易的。

參考資料:
(CORS) 啟用跨域請求 ASP.NET Core

GitHub:
https://github.com/dotnet/aspnetcore/tree/master/src/Mvc/Mvc/src
https://github.com/dotnet/aspnetcore/tree/master/src/Mvc/Mvc.Cors/src
https://github.com/dotnet/aspnetcore/tree/master/src/Middleware/CORS/src


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 百度雲盤:Python入門經典以解決計算問題為導向的Python編程實踐PDF高清完整版免費下載 提取碼:6e8d 內容簡介 《Python入門經典:以解決計算問題為導向的Python編程實踐》是一本系統而科學的Python入門教程,美國密歇根州立大學等多所美國知名高校採用其作為編程語言的入門教材, ...
  • 大量使用的對象,重覆的創建和銷毀,很耗費性能,這個時候就要使用對象池技術。 ...
  • 一、函數嵌套 1.函數的嵌套調用 在調用一個函數的過程中又調用其他函數 將一個大工能拆解成很多小功能 每個函數名都是全局變數,可以在全局有效 2.函數的嵌套定義 在函數內定義其他函數 子函數只能能在函數中被使用,子函數名只在局部有效 最外層函數相當於一個容器,裝了很多子函數 3.函數的嵌套調用和嵌套 ...
  • 百度網盤:Python項目開發實戰(第2版)PDF高清完整版免費下載 提取碼:exep 內容簡介 本書來自真正的開發現場,是BePROUD公司眾多極客在真實項目中的經驗總結和智慧結晶。作者從Python的環境搭建開始講起,介紹了Web應用的開發方法、項目管理及審查、測試與高效部署、伺服器調試等內容, ...
  • 前言 本文的文字及圖片來源於網路,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。 作者:彥頁走刀口 今天我們來看看用ghpython怎麼實現koch曲線的分形效果,前兩天分享的雪花分形是利用grasshopper的迴圈插件anemone實現的,然後有個小伙 ...
  • 前言 在朋友的項目有個自定義配置文件user.yml,其內容如下 user: userId: 1 name: 張三 email: [email protected] 其映射實體內容為如下 @Data @AllArgsConstructor @NoArgsConstructor @Builder @Pro ...
  • 在我們開發各種應用的時候,都會碰到很多不同的問題,這些問題涉及架構、模塊組合、界面處理、共同部分抽象等方面,我們這裡以Winform開發為例,從系統模塊化、界面組件選擇、業務模塊場景劃分、界面基類和輔助類處理、代碼生成工具輔助開發等方面介紹在實際項目開發過程中碰到的困境和相關的解決方案,以便分析其中... ...
  • 1、資料庫 根據需要附加或者還原U9對應資料庫; 2、配置IIS ①:打開IIS管理器,在網站預設節點(Default Web Site),添加應用程式; ②:配置應用程式(文件夾必須是非漢字命名); 3、Classview訪問 Classview訪問(部署在本地訪問地址為:http://127.0 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...