深入剖析.NETCORE中CORS(跨站资源共享)
前言
由于現(xiàn)代互聯(lián)網(wǎng)的飛速發(fā)展,我們?cè)陂_發(fā)現(xiàn)代 Web 應(yīng)用程序中,經(jīng)常需要考慮多種類型的客戶端訪問服務(wù)的情況;而這種情況放在15年前幾乎是不可想象的,在那個(gè)時(shí)代,我們更多的是考慮怎么把網(wǎng)頁快速友好的嵌套到服務(wù)代碼中,經(jīng)過服務(wù)器渲染后輸出HTML到客戶端,沒有 iOS,沒有 Android,沒有 UWP。更多的考慮是 防止 XSS,在當(dāng)時(shí)的環(huán)境下,XSS一度成為各個(gè)站長(zhǎng)的噩夢(mèng),甚至網(wǎng)站開發(fā)的基本要求都要加上:必須懂防 XSS 攻擊。
CORS 定義
言歸正傳,CORS(Cross-Origin Resource Sharing)是由 W3C 指定的標(biāo)準(zhǔn),其目的是幫助在各個(gè)站點(diǎn)間的資源共享。CORS 不是一項(xiàng)安全標(biāo)準(zhǔn),啟用 CORS 實(shí)際上是讓站點(diǎn)放寬了安全標(biāo)準(zhǔn);通過配置 CORS,可以允許配置中的請(qǐng)求源執(zhí)行允許/拒絕的動(dòng)作。
在 .NETCore 中啟用 CORS
在 .NETCore 中,已經(jīng)為我們集成好 CORS 組件 Microsoft.AspNetCore.Cors,在需要的時(shí)候引入該組件即可,Microsoft.AspNetCore.Cors 的設(shè)計(jì)非常的簡(jiǎn)潔,包括兩大部分的內(nèi)容,看圖:
從上圖中我們可以看出,左邊是入口,是我們常見的 AddCors/UseCors,右邊是 CORS 的核心配置和驗(yàn)證,配置對(duì)象是 CorsPolicyBuilder 和 CorsPolicy,驗(yàn)證入口為 CorsService,中間件 CorsMiddleware 提供了攔截驗(yàn)證入口。
CorsService 是整個(gè) CORS 的核心實(shí)現(xiàn),客戶端的請(qǐng)求流經(jīng)中間件或者AOP組件后,他們?cè)趦?nèi)部調(diào)用 CorsService 的相關(guān)驗(yàn)證方法,在 CorsService 內(nèi)部使用配置好的 PolicyName 拉去相關(guān)策略進(jìn)行請(qǐng)求驗(yàn)證,最終返回驗(yàn)證結(jié)果到客戶端。
Microsoft.AspNetCore.Mvc.Cors
通常情況下,我們會(huì)在 Startup 類中的 ConfigureServices(IServiceCollection services) 方法內(nèi)部調(diào)用 AddCors() 來啟用 CROS 策略,但是,該 AddCors() 并不是上圖中 CorsServiceCollectionExrensions 中的 AddCors 擴(kuò)展方法。
實(shí)際上,在 ConfigureServices 中調(diào)用的 AddCors 是處于程序集 Microsoft.AspNetCore.Mvc.Cors ;在 Microsoft.AspNetCore.Mvc.Cors 內(nèi)部的擴(kuò)展方法 AddCors() 中,以 AOP 方式定義了對(duì) EnableCorsAttribute/DisableCorsAttributeAttribute 的攔截檢查。
具體做法是在程序集 Microsoft.AspNetCore.Mvc.Cors 內(nèi)部,定義了類 CorsApplicationModelProvider ,當(dāng)我們調(diào)用 AddCors 擴(kuò)展方法的時(shí)候,將進(jìn)一步調(diào)用 CorsApplicationModelProvider.OnProvidersExecuting(ApplicationModelProviderContext context) 方法,從而執(zhí)行檢查 EnableCorsAttribute/DisableCorsAttributeAttribute 策略。
所以,我們?cè)?ConfigureServices 中調(diào)用的 AddCore,其實(shí)是在該程序集內(nèi)部定義的類:MvcCorsMvcCoreBuilderExtensions 的擴(kuò)展方法,我們看 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>();} }重點(diǎn)就在上面的 AddCorsServices(IServiceCollection services) 方法中, 在方法中調(diào)用了 CORS 的擴(kuò)展方法 AddCors()。
那么我們就要問, CorsApplicationModelProvider 是在什么時(shí)候被初始化的呢?答案是在 startup 中 ConfigureServices(IServiceCollection services) 方法內(nèi)調(diào)用 services.AddControllers() 的時(shí)候。在AddControllers() 方法內(nèi)部,調(diào)用了 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 的執(zhí)行過程,下面我們就可以開始了解應(yīng)該怎么在 .NETCore 中使用 CORS 的策略了
CORS 啟用的三種方式
在 .NETCore 中,可以通過以下三種方式啟用 CORS
1、使用默認(rèn)策略/命名策略的中間件的方式 2、終結(jié)點(diǎn)路由 + 命名策略 3、命名策略 + EnableCorsAttribute
通過上面的三種方式,可以靈活在程序中控制請(qǐng)求源的走向,但是,殘酷的事實(shí)告訴我們,一般情況下,我們都是會(huì)對(duì)全站進(jìn)行 CORS。所以,現(xiàn)實(shí)情況就是在大部分的 Web 應(yīng)用程序中, CORS 已然成為皇帝的新裝,甚至有點(diǎn)累贅。
CorsPolicyBuilder(CORS策略)
通過上面的 CORS 思維導(dǎo)圖,我們已經(jīng)大概了解了 CORS 的整個(gè)結(jié)構(gòu)。由上圖我們知道,CorsPolicyBuilder 位于命名空間 Microsoft.AspNetCore.Cors.Infrastructure 中。在內(nèi)部提供了兩種基礎(chǔ)控制策略:全開/半開。這兩種策略都提供了基本的方法供開發(fā)者直接調(diào)用,非常的貼心。
全開
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);上面的策略定義從字面理解就可以知道其用途,實(shí)際上呢,他們的實(shí)現(xiàn)原理也是非常的簡(jiǎn)單。在 CorsPolicyBuilder 內(nèi)部維護(hù)著一個(gè) CorsPolicy 對(duì)象,當(dāng)你使用全開/半開方式配置策略的時(shí)候,builder 會(huì)將配置寫入內(nèi)部 CorsPolicy 中存儲(chǔ)備用。
比如半開 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; }開始使用
在理解了配置的過程后,我們就可以進(jìn)入真正的使用環(huán)節(jié)了,通過上面的學(xué)習(xí)我們知道,啟用 CORS 有三種方式,咱們一步一步來。
使用默認(rèn)策略/命名策略的中間件的方式
所謂的命名策略就是給你的策略起個(gè)名字,默認(rèn)策略就是沒有名字,所有的入口都使用同一個(gè)策略,下面的代碼演示了命名策略
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();}); }上面的代碼演示了如何在站點(diǎn)中全局終結(jié)點(diǎn)啟用 CORS,首先聲明了命名策略 corsalloworgins ,然后將其用 AddCors() 添加到 CORS 中,最后使用 UseCors() 啟用該命名策略,需要注意的是,AddCors() 和 UseCors() 必須成對(duì)出現(xiàn),并且要使用同一個(gè)命名策略。
終結(jié)點(diǎn)路由 + 命名策略
.NETCore 支持通過對(duì)單個(gè)路由設(shè)置 CORS 命名策略,從而可以實(shí)現(xiàn)在一個(gè)系統(tǒng)中,對(duì)不同的業(yè)務(wù)提供個(gè)性化的支持。終結(jié)點(diǎn)路由 + 命名策略的配置和上面的命名策略基本相同,僅僅是在配置路由的時(shí)候,只需要對(duì)某個(gè)路由增加 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 需要執(zhí)行 CORS 策略 CORSALLOWORGINS。通過調(diào)用 RequireCors() 方法,傳入策略名稱,完成 CORS 的配置。RequireCors 方法是在程序集 Microsoft.AspNetCore.Cors 內(nèi)部的擴(kuò)展方法,具體是怎么啟用策略的呢,其實(shí)就是在內(nèi)部給指定的終結(jié)點(diǎn)路由增加了 EnableCorsAttribute ,這就是下面要說到的第三種啟用 CORS 的方式。
來看看 RequireCors() 內(nèi)部的代碼
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 特性標(biāo)記,和 RequireCors 方法內(nèi)部的實(shí)現(xiàn)不同的是,這里說的 EnableCorsAttribute 是顯式的指定到控制器上,在應(yīng)用 EnableCorsAttribute 的時(shí)候,你可以應(yīng)用到根控制器或者子控制器上,如果是對(duì)根控制器進(jìn)行標(biāo)記,被標(biāo)記的根控制器和他的所有子控制器都將受指定 CORS 策略的影響;反之,如果只是對(duì)子控制器進(jìn)行標(biāo)記,CORS 策略也只對(duì)當(dāng)前控制器產(chǎn)生影響。
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();}); }在上面的代碼中,因?yàn)?EnableCorsAttribute 可以應(yīng)用到類和屬性上,所以我們定義了兩個(gè) CORS 策略,分別是 controllercors 和 actioncors。接下來將這兩種策略應(yīng)用到 WeatherForecastController 上。
應(yīng)用 EnableCorsAttribute 特性標(biāo)記
[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 控制器中,我們將 controllercors 標(biāo)記到控制器上,將 actioncors 標(biāo)記到 Action 名稱為 Users 上面,同時(shí),還對(duì) List 應(yīng)用了 DisableCors ,表示對(duì) List 禁用 CORS 的策略,所以我們知道,在 CORS 中,有 AddCors/UseCors,也有 EnableCors/DisableCors ,都是成對(duì)出現(xiàn)的。
其它策略
我們還記得,在 .NETCore 中,一共有 4 種策略,分別是:Header、Method、Origin、Credentials,但是本文僅演示了 WithOrigins 這一種方式,相信通過這一種方式的演示,對(duì)大家在啟用其它策略的時(shí)候,其思想也是一致的,所謂的標(biāo)頭、請(qǐng)求方式、憑據(jù) 等等,其基本法是不變的。
通過對(duì) Microsoft.AspNetCore.Cors 的內(nèi)部實(shí)現(xiàn)的剖析,我們了解到,其實(shí)現(xiàn) CORS 的原理非常簡(jiǎn)單,結(jié)構(gòu)清晰,就算不用系統(tǒng)自帶的 CORS 組件,自行實(shí)現(xiàn)一個(gè) CORS 策略,也是非常容易的。
參考資料:(CORS) 啟用跨域請(qǐng)求 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
總結(jié)
以上是生活随笔為你收集整理的深入剖析.NETCORE中CORS(跨站资源共享)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三分钟Docker-镜像、容器实战篇
- 下一篇: 【Power Automate】如何自动