深入剖析.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
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...