在ABP的Web层中实现复杂请求跨域访问

在最近的项目中,后端使用ABP,前端采用React,先后端彻底分离。其中大部分接口都经过WebApi层调用,项目中未使用Session。但最后在添加一个网站的验证码验证留言功能时,使用了Session验证的方式,因此将验证码请求与校验功能放在了Web层。因为测试阶段先后端不一样域,涉及到跨域请求的问题。跨域问题能够经过代理等手段解决,可是也能够在后端作些简单的修改来进行实现。WebApi的跨域处理比较简单,有官方给出的解决方案Microsoft.AspNet.WebApi.Cors。可是Web层通常不涉及跨域,因此本身进行了探索实现。 ##1、常见方案html

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

##2、常见方案问题分析前端

  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都没有经过,何谈后续的跨域请求?!

##3、增长对复杂请求的预检(Preflight,即Options请求)处理支持 asp.net的web层,Options请求是在哪里进行处理?到达控制器中的action时,已是正式请求了,最终发现应该能够在Global.asax中,经过Application_BeginRequest方法进行处理。web

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跨域携带的问题,咱们来作进一步的改造。ajax

##4、携带Cookie跨域json

  1. 修改Global.aspx中的Application_BeginRequest方法,增长代码<font style="color: #AD5D0F;">Response.Headers.Add("Access-Control-Allow-Credentials", "true");</font>
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方法, 增长代码<font style="color: #AD5D0F;">filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Credentials", "true");</font>,另外须要注意<font style="color: #AD5D0F;">filterContext.RequestContext.HttpContext.Response.AppendHeader("Access-Control-Allow-Origin", host);</font>,代码中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": "一二三四"}');
    }
相关文章
相关标签/搜索