文章出處

更新:后來有人在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")));
}

一勞永逸的目標就此達成,不舒服的感覺煙消云散。


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()