更新:后來有人在github上提交了 pull request,后來ASP.NET Core已經支持使用通配符域名配置CORS。
ASP.NET Core 內置了對 CORS 的支持,使用很簡單,只需先在 Startup 的 ConfigureServices() 中添加 CORS 策略:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins( "http://www.cnblogs.com", "https://q.cnblogs.com", "https://zzk.cnblogs.com", "https://i.cnblogs.com", "https://news.cnblogs.com", "https://job.cnblogs.com"))); }
然后在想啟用 CORS 的控制器 Action 上應用這個策略:
[EnableCors("AllowSameDomain")] public IActionResult Markdown() { return View(); }
但是,當看到上面一堆網址時,當想到每增加一個二級域名都需要修改上面的代碼時,一種不舒服的感覺油然而生,一種想偷懶的沖動涌上心頭。
難道沒有一勞永逸的方法嗎?DNS解析中支持在域名中使用通配符(*.cnblogs.com),CA證書中也支持,如果這里的 CORS 策略也支持使用通配符,不就可以一勞永逸了嗎?配置 CORS 策略的代碼就可以簡化為下面的樣子:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins("*.cnblogs.com"))); }
不僅一勞永逸,而且代碼更加簡潔漂亮。
可是負責 ASP.NET CORS 的開發者沒這么貼心,只能自己動手了。
從 github 簽出 ASP.NET CORS 的源代碼,找到其中根據域名進行判斷的實現代碼:
public class CorsService : ICorsService { public virtual void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin)) { return; } AddOriginToResult(origin, policy, result); result.SupportsCredentials = policy.SupportsCredentials; AddHeaderValues(result.AllowedExposedHeaders, policy.ExposedHeaders); } public virtual void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; if (StringValues.IsNullOrEmpty(origin) || !policy.AllowAnyOrigin && !policy.Origins.Contains(origin)) { return; } //... } }
(這里竟然有重復代碼,又增添了一份不舒服的感覺,暫且不管)
原來是通過 !policy.Origins.Contains(origin) 判斷的,只要修改這個地方的判斷代碼,就能實現一勞永逸的偷懶目的。但是,這部分代碼不是隨意可以修改的,要走代碼貢獻流程,而且不一定被接受,目前還是先想辦法擴展它吧。
英明的 ASP.NET CORS 開發者早就考慮了這個地方的擴展性,將 EvaluateRequest() 與 EvaluatePreflightRequest 定義為虛擬方法,我們只需定義一個子類繼承自 CorsService ,覆蓋這兩個方法即可。
接下來就是解決如何覆蓋的問題。把父類中的實現代碼復制過來修改不可取,以后 ASP.NET CORS 升級了,這部分代碼改了,就會帶來問題。我們需要想辦法在不改變現有處理邏輯的前提下,影響處理結果。
我們繼續看 !policy.Origins.Contains(origin) ,policy.Origins 的類型是 IList<string> ,它存儲的就是我們在定義 CORS 策略時添加的網址,所以,如果我們想要影響這里的判斷結果,唯有改變 policy.Origins 的值。
根據當前的情況,我們可以把問題簡化為下面的代碼:
public static void Main(string[] args) { IList<string> origins = new List<string>() { "*.cnblogs.com" }; var origin = "http://www.cnblogs.com"; //在origins中添加"http://www.cnblogs.com" Assert.True(origins.Contains(origin)); }
接下來只需做一件事——集中精力把上面的注釋變成代碼。
。。。
我們將注釋變成了下面的代碼:
private void EvaluateOriginForWildcard(IList<string> origins, string origin) { //只在沒有匹配的origin的情況下進行操作 if (!origins.Contains(origin)) { //查詢所有以星號開頭的origin var wildcardDomains = origins.Where(o => o.StartsWith("*")); if (wildcardDomains.Any()) { //遍歷以星號開頭的origin foreach (var wildcardDomain in wildcardDomains) { //如果以.cnblogs.com結尾 if (origin.EndsWith(wildcardDomain.Substring(1)) //或者以//cnblogs.com結尾,針對http://cnblogs.com || origin.EndsWith("//" + wildcardDomain.Substring(2))) { //將http://www.cnblogs.com添加至origins origins.Add(origin); break; } } } } }
然后基于上面的代碼實現 CorsService 的子類 WildcardCorsService :
public class WildcardCorsService : CorsService { public WildcardCorsService(IOptions<CorsOptions> options) : base(options) { } public override void EvaluateRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; EvaluateOriginForWildcard(policy.Origins, origin); base.EvaluateRequest(context, policy, result); } public override void EvaluatePreflightRequest(HttpContext context, CorsPolicy policy, CorsResult result) { var origin = context.Request.Headers[CorsConstants.Origin]; EvaluateOriginForWildcard(policy.Origins, origin); base.EvaluatePreflightRequest(context, policy, result); } private void EvaluateOriginForWildcard(IList<string> origins, string origin) { //... } }
然后將其注入:
services.Add(ServiceDescriptor.Transient<ICorsService, WildcardCorsService>());
(注:這里要用 Add ,不要用 TryAdd ,因為在 service.AddMvc 中已經把 CorsService 注入了,用 Add 才能覆蓋 CorsService 的注入。)
最終 ConfigureServices() 中的代碼變成了這樣:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.Add(ServiceDescriptor.Transient<ICorsService, WildcardCorsService>()); services.Configure<CorsOptions>(options => options.AddPolicy( "AllowSameDomain", builder => builder.WithOrigins("*.cnblogs.com"))); }
一勞永逸的目標就此達成,不舒服的感覺煙消云散。
文章列表