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()));
  • 相关阅读:
    String与StringBuffer常用API
    Java常用类库与工具课后习题3-5
    Java异常课后习题编程题
    Java异常课后习题简答题
    Visio的快速使用和功能理念
    Google免费新书-《构建安全&可靠的系统》
    名字的由来
    日志分析-利用grep,awk等文本处理工具完成(2019-4-9)
    关于docker--详解安装,常规操作,导入导出等(2017-3-29)
    Thinkphp5-0-X远程代码执行漏洞分析(2019-1-11)
  • 原文地址:https://www.cnblogs.com/dudu/p/5895424.html
Copyright © 2011-2022 走看看