深入剖析.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
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...