在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": "一二三四"}');
    }
相關文章
相關標籤/搜索