一勞永逸:域名支持通配符,ASP.NET Core中配置CORS

ASP.NET Core 內置了對 CORS 的支持,使用很簡單,只需先在 Startup 的 ConfigureServices() 中添加 CORS 策略:git

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 上應用這個策略:github

[EnableCors("AllowSameDomain")]
public IActionResult Markdown()
{
    return View();
}

可是,當看到上面一堆網址時,當想到每增長一個二級域名都須要修改上面的代碼時,一種不舒服的感受油然而生,一種想偷懶的衝動涌上心頭。ide

難道沒有一勞永逸的方法嗎?DNS解析中支持在域名中使用通配符(*.cnblogs.com),CA證書中也支持,若是這裏的 CORS 策略也支持使用通配符,不就能夠一勞永逸了嗎?配置 CORS 策略的代碼就能夠簡化爲下面的樣子:ui

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options => options.AddPolicy(
        "AllowSameDomain",
        builder => builder.WithOrigins("*.cnblogs.com")));
}

不只一勞永逸,並且代碼更加簡潔漂亮。lua

但是負責 ASP.NET CORS 的開發者沒這麼貼心,只能本身動手了。spa

從 github 簽出 ASP.NET CORS 的源代碼,找到其中根據域名進行判斷的實現代碼code

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;
        }

        //...
    }
}

(這裏居然有重複代碼,又增添了一份不舒服的感受,暫且無論)blog

原來是經過 !policy.Origins.Contains(origin) 判斷的,只要修改這個地方的判斷代碼,就能實現一勞永逸的偷懶目的。可是,這部分代碼不是隨意能夠修改的,要走代碼貢獻流程,並且不必定被接受,目前仍是先想辦法擴展它吧。繼承

英明的 ASP.NET CORS 開發者早就考慮了這個地方的擴展性,將 EvaluateRequest() 與 EvaluatePreflightRequest 定義爲虛擬方法,咱們只需定義一個子類繼承自 CorsService ,覆蓋這兩個方法便可。ip

接下來就是解決如何覆蓋的問題。把父類中的實現代碼複製過來修改不可取,之後 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")));
}

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

相關文章
相關標籤/搜索