zoukankan      html  css  js  c++  java
  • 在ABP的Web层中实现复杂请求跨域访问

    在最近的项目中,后端使用ABP,前端采用React,前后端完全分离。其中大部分接口都通过WebApi层调用,项目中未使用Session。但最后在添加一个网站的验证码验证留言功能时,使用了Session验证的方式,所以将验证码请求与校验功能放在了Web层。由于测试阶段前后端不同域,涉及到跨域请求的问题。跨域问题可以通过代理等手段解决,但是也可以在后端做些简单的修改来进行实现。WebApi的跨域处理比较简单,有官方给出的解决方案Microsoft.AspNet.WebApi.Cors。但是Web层一般不涉及跨域,所以自己进行了探索实现。

    一、常见方案

    1. 在web.config中添加配置。
    <system.webServer>
            <httpProtocol> 
                <customHeaders> 
                    <add name="Access-Control-Allow-Methods" value="OPTIONS,POST,GET"/> 
                    <add name="Access-Control-Allow-Headers" value="x-requested-with"/> 
                    <add name="Access-Control-Allow-Origin" value="*" /> 
                </customHeaders> 
            </httpProtocol> 
    </system.webServer>
    
    1. 在被访问的控制器上加上[AllowCrossSiteJson("localhost:3000")]的Attribute。
      AllowCrossSiteJsonAttribute类代码如下:
    public class AllowCrossSiteJsonAttribute : ActionFilterAttribute
        {
            private string[] _domains;
            public AllowCrossSiteJsonAttribute(string domain)
            {
                _domains = new string[] { domain };
            }
            public AllowCrossSiteJsonAttribute(string[] domains)
            {
                _domains = domains;
            }
            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                var context = filterContext.RequestContext.HttpContext;
                var host = context.Request.Headers.Get("Origin");
                if (host != null&& _domains.Contains(host))
                {
                    //域
                    filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", host);
                    //Http方法
                    filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Methods", "*");
                }
                base.OnActionExecuting(filterContext);
            }
        }
    

    二、常见方案问题分析

    1. 上面两种常见方案,方案一堪称简单粗暴,方案二则有针对性的开放一些域来进行跨域访问,比如localhost:3000
    2. 上面两种方案,都存在一个致命的问题,仅对简单跨域请求有效,无法处理复杂的跨域请求。
      • 那么何为复杂的跨域请求?可以参考阮一峰的科普http://www.ruanyifeng.com/blog/2016/04/cors.html。
        比如我们常用的Post或Put请求,Content-Type字段的类型一般是application/json时,就是复杂请求。
      • 复杂请求会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错,而这次preflight的Http方法就是Options。换句话说,如果你的xhr请求发出前,会先发出一个Options请求,就说明你要执行的请求是复杂请求。
      • 对于复杂的跨域请求,如果连preflight都没有通过,何谈后续的跨域请求?!

    三、增加对复杂请求的预检(Preflight,即Options请求)处理支持

    asp.net的web层,Options请求是在哪里进行处理?到达控制器中的action时,已经是正式请求了,最终发现应该可以在Global.asax中,通过Application_BeginRequest方法进行处理。

    protected override void Application_BeginRequest(object sender, EventArgs e)
    {
        if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")//拦截处理Options请求
        {
            string domain = Request.Headers.Get("Origin");
            //
            //这里可以对domain进行校验,即维护一个可跨域访问的列表,进行比对,校验通过后才执行下面的操作。本文中不做处理。
            //
            Response.Headers.Add("Access-Control-Allow-Origin", domain);
            Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,authorization");//authorization是我项目中要用到的,读者可以忽略
            Response.Flush();
            Response.End();
        }
        base.Application_BeginRequest(sender, e);
    }
    

    这样,我们对Options跨域请求进行了“可支持跨域”的应答。之后的正式请求到达控制器中的Action,又有相应的跨域访问处理。那么对于整个的复杂请求跨域就完成实现了。
    但是,上文中我们提到,要实现的是验证码Session验证功能,那么就还涉及到Cookie跨域携带的问题,我们来做进一步的改造。

    四、携带Cookie跨域

    1. 修改Global.aspx中的Application_BeginRequest方法,增加代码Response.Headers.Add("Access-Control-Allow-Credentials", "true");
    protected override void Application_BeginRequest(object sender, EventArgs e)
    {
            if (Request.Headers.AllKeys.Contains("Origin") && Request.HttpMethod == "OPTIONS")//拦截处理Options请求
            {
                string domain = Request.Headers.Get("Origin");
                //
                //这里可以对domain进行校验,即维护一个可跨域访问的列表,进行比对,校验通过后才执行下面的操作。本文中不做处理。
                //
                Response.Headers.Add("Access-Control-Allow-Origin", domain);
                Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,authorization");//authorization是我项目中要用到的,读者可以忽略
                Response.Headers.Add("Access-Control-Allow-Credentials", "true");//可携带Cookie
                Response.Flush();
                Response.End();
            }
            base.Application_BeginRequest(sender, e);
    }
    
    1. 修改AllowCrossSiteJsonAttribute类的OnActionExecuting方法,
      增加代码filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Credentials", "true");,另外需要注意filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", host);,代码中host不能用*来代替,必须使用具体的host名称,这是跨域携带cookie的要求。
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
            var context = filterContext.RequestContext.HttpContext;
            var host = context.Request.Headers.Get("Origin");
            if (host != null&& _domains.Contains(host))
            {
                //域,带cookie请求必须明确指定host,不能使用*代替
                filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", host);
                //Http方法
                filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Methods", "*");
                //可携带cookie
                filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Credentials", "true");
            }
            base.OnActionExecuting(filterContext);
    }
    

    至此,我们完成了在asp.net MVC中复杂请求携带cookie跨域的处理。下面给出前端的调用代码以供参考。
    Ajax版本

        $.ajax({
            url: 'http://192.168.100.66:3006/OnlineMessage',
            type: 'post',
            xhrFields: {
                withCredentials: true
            },
            dataType: 'application/json; charset=utf-8',
            data: {
                "author": "1",
                "qq": "2",
                "phone": "3",
                "email": "4",
                "content": "留言",
                "checkCode": "一二三四"
            },
            complete: function (data) {
                alert(JSON.stringify(data));
            }
        });
    

    Xhr版本

        function loadXMLDoc() {
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "http://192.168.100.66:3006/OnlineMessage");
            xhr.setRequestHeader("Content-type", "application/json");
            xhr.withCredentials = true;
            xhr.send('{"author": "1","qq": "2","phone": "3","email": "4","content": "留言","checkCode": "一二三四"}');
        }
    
  • 相关阅读:
    wp8开发时模拟器无法联网解决方法
    软件测试技术---白盒测试
    软件测试技术---代码检查,走查与评审
    简谈WP,IOS,Android智能手机OS
    软件测试技术---在软件生命周期中测试的实施
    软件测试技术---测试的基本概念
    zookeeper源码分析三LEADER与FOLLOWER同步数据流程
    zookeeper源码分析(一) 工作原理
    分布式服务框架 Zookeeper -- 管理分布式环境中的数据
    构建高并发高可用的电商平台架构实践(转)
  • 原文地址:https://www.cnblogs.com/jiujiduilie/p/8442682.html
Copyright © 2011-2022 走看看