zoukankan      html  css  js  c++  java
  • 一劳永逸:域名支持通配符,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")));
    }

    一劳永逸的目标就此达成,不舒服的感觉烟消云散。

    【更新】

    后来 ASP.NET Core 提供了内置支持,示例代码如下

    services.AddCors(options => options.AddPolicy(
        "CnblogsCors",
        p => p.SetIsOriginAllowedToAllowWildcardSubdomains()
            .WithOrigins("https://*.cnblogs.com", "http://*.cnblogs.com")
            .AllowAnyMethod().AllowAnyHeader()));
  • 相关阅读:
    nowcoderD Xieldy And His Password
    Codeforces681D Gifts by the List
    nowcoder80D applese的生日
    Codeforces961E Tufurama
    Codeforces957 Mahmoud and Ehab and yet another xor task
    nowcoder82E 无向图中的最短距离
    nowcoder82B 区间的连续段
    Codeforces903E Swapping Characters
    Codeforces614C Peter and Snow Blower
    Codeforces614D Skills
  • 原文地址:https://www.cnblogs.com/dudu/p/5895424.html
Copyright © 2011-2022 走看看