簡析ASP.NET WebApi的跨域簽名

要弄清楚 CORS規範將哪些類型的跨域資源請求劃分爲簡單請求的範疇,需要額外瞭解幾個名稱的含義,其中包括 “簡單 (HTTP)方 法 (Simple Method) “、“簡單(請)報頭 (Simple Hader)” 和 “ 自定義請求報頭 (Author Request Header/ Custom Request Header)” 。 

  CORS規範將GET、HEAD、POST這三個HTTP方法視爲“簡單HTTP方法”,而將請求報頭Accept、Accept-Language、Content-Language以及Content-Type採用Application/X-www-form-urlencoded、multipart/form-data、text/plain的報頭稱爲簡單請求報頭。

  簡而言之,簡單請求就是隻包含簡單請求報頭的採用簡單方法的Http請求,其他的請求即爲費簡單請求,如本文提到的跨域簽名所需要的簽名參數就是添加在HTTP請求的自定義頭部裏面(如果把簽名信息包含在處理函數的參數裏面就顯得接口簽名做的很LOW)。

  跨域其實說的是瀏覽器的同源策略對JavaScript腳本的Ajax請求的一些限制,阻止程序對腳本請求的數據的操作,而不是阻止請求的發送以及數據的接收,如果用抓包工具查看網絡數據的話,其實可以很明顯的看到請求的發送,以及接口正常的數據返回。但是爲什麼瀏覽器不能正常的處理數據呢?這是因爲瀏覽器需要得到資源提供者的授權之後纔會把資源分發給消費者(即JavaScript腳本),而Ajax在進行跨域資源的請求的時候會在報頭添加一個“Origin”頭部,這個頭部的值就是當前發起請求的域。因此想要解決簡單請求的跨域問題只需要對請求報文中的Origin的值進行處理,對已授權的域的響應添加一個響應報頭"Access-Control-Allow-Origin",一般對授權域的響應報頭"Access-Control-Allow-Origin"值置爲“*”。而非簡單請求的跨域調用流程就跟簡單報文的調用流程有所差異呢,在發送非簡單報文時,瀏覽器就會採用“預檢”機制來完成非簡單請求的跨域資源請求。所謂預檢機制就是瀏覽器在發送真正的跨域資源請求前 ,先發送一個預檢請求(PreflightRequest)。 預檢請求爲一個採用0PTIONS方法的請求,這是一個不包含主體的請求,用戶憑證相關的報頭也會被剔除。基於真正資源請求的一些輔助授權的信息會包含在此預檢請求的響應報頭中。 除了代表請求頁面所在站點的 “Origin” 報頭之外,如 下所示的是兩個典型的請求報頭 。
● Access-Control-Request-Method:跨域資源請求採用的HTTP方 法 。
● Access-Control-Request-Headers:跨域資源請求攜帶的自定義報頭列表 。
資源的提供者在接收到預檢請求之後會根據其提供的相關報頭進行授權檢驗 ,具體的檢驗邏輯包括確定請求站點是否值得信任 ,以及請求採用HTTP方法和自定義報頭是否被允許 。如果預檢請求沒有通過授權檢驗 ,資源提供者一般會返回一個狀態爲“400,Bad Reuqest”的響應(也可自定義返回報文消息,如本文) 。 反之則會返回一個狀態爲 “200,OK” 的響應(也可自定義返回報文消息) ,授權相關信息會包含在響應報頭中。
除了上面介紹的 “Access-Control-Allow-Origin” 和 “Access-Control-Allow-Method” 報頭之外,預檢請求的響應還具有如下 3個典型的報頭。
● Access-Control-Allow-Method:跨域資源請求允許採用 的 HTTP方法列表 。
● Access-Control-Allow-Headers:跨域資源請求允許攜帶的自定義報頭列表 。

● Access-Control-Max-Age:瀏覽器可以將響應結果進行緩存的時間 (單位爲秒 ),這樣可以讓瀏覽器避免頻繁地發送預檢請求 。

如果預檢請求滿足如下三個條件,瀏覽器則認爲後續將要發送的跨域資源請求是被授權的

● 通過請求的 “Origin” 報頭表示的源站點必須存在於 “Access-Control-Allow-Origin響應報頭標識的站點列表中。

預檢請求的 “Access-Control-Request-Headers” 報頭存儲的報頭名稱均在響應報頭“Access-Control-Allow-Headers” 表示的報頭列表之內。

預檢請求的“Access-Control-Request-Method” 報頭表示的請求方法在預檢請求響應報文“Access-Control-Allow-Methods”表示的列表之內。   

  因此以上可知:想要完成非簡單報文的跨域請求,就必須要在服務器端對預檢請求進行正常的應答,當收到預檢請求時,對其進行正常的響應,這就需要在應答報文中添加“Access-Control-Allow-Origin”、“Access-Control-Allow-Methods”、“Access-Control-Allow-Headers”,這三個自定義報文頭,同時這三個報文頭要按照預檢機制的要求進行填充。尤其是“Access-Control-Allow-Headers”自定義頭部列表要包含簽名所需的所有自定義頭部名。

  以上便是對跨域、預檢機制以及其解決方法的描述,接下來講實際的處理方法

  (1)跨域支持:對控制器添加一個Filter屬性並重寫OnActionExecuted方法,向響應報文添加自定義頭部。

    (2)預檢報文應答:因爲一般的API控制器都沒有實現OPTIONS方法,而預檢報文的請求方法是OPTIONS,因此需要我們實現OPTION方法。至於每個控制器多了一個平時都不使用的OPTIONS方法,寫在那裏難看,這就是系統架構要考慮的問題了,這裏我們只說具體問題的具體解決方法。

  (3)接口簽名驗證:對控制器添加一個Filter屬性對自定義報文進行自己的驗證邏輯,這個就隨意發揮了。 

  跨域的預檢報文支持代碼:

    /// <summary>
    /// 添加跨域支持
    /// </summary>
    public class EnableCors : ActionFilterAttribute
    {
        /// <summary>
        /// 操作標記
        /// </summary>
        private bool Flag { get;  set; }

        /// <summary>
        /// 默認構造函數 true:開啓跨域 false:關閉跨域支持
        /// </summary>
        /// <param name="para"></param>
        public EnableCors(bool para)
        {
            Flag = para;
        }

        /// <summary>
        /// 方法執行之後執行
        /// </summary>
        /// <param name="actionExecutedContext"></param>
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            base.OnActionExecuted(actionExecutedContext);
            if (Flag != true) return;
            if (actionExecutedContext.Response == null) return;
            if (actionExecutedContext.Response.Headers.Contains("Access-Control-Allow-Origin"))
            {
                actionExecutedContext.Response.Headers.Remove("Access-Control-Allow-Origin");
            }
            if (actionExecutedContext.Response.Headers.Contains("Access-Control-Allow-Method"))
            {
                actionExecutedContext.Response.Headers.Remove("Access-Control-Allow-Method");
            }
            if (actionExecutedContext.Response.Headers.Contains("Access-Control-Allow-Headers"))
            {
                actionExecutedContext.Response.Headers.Remove("Access-Control-Allow-Headers");
            }
            actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Origin", "*");
            actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            actionExecutedContext.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,TimeStamp,Parameter,RandNum");
        }
    }

  接口簽名代碼:

   /// <summary>
    /// 接口簽名屬性
    /// </summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
    public class ApiAuthorization : AuthorizationFilterAttribute
    {
        public override void OnAuthorization(HttpActionContext actionContext)
        {
            base.OnAuthorization(actionContext);
            if (actionContext.Request.Method == HttpMethod.Options) return;//支持跨域的自定義報頭請求(預檢機制)
            //TODO這裏就是接口簽名的代碼,可以取自定義頭部進行處理,簽名通過則直接return,否則對actionContext.Response進行賦值,表示簽名失敗後面的操作不執行
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, new CheckResult() { Result = false, Message = "Access denied!" }); 
        }
    }

  

 

    /// <summary>
    /// 基礎控制器
    /// </summary>
    [DataType(ApiDataType.Json)]
    [EnableCors(true)]
    [Description("Api基礎控制器")]
    [ApiAuthorization]
    public class ApiBaseController : ApiController
    {
        /// <summary>
        /// 爲了支持ajax跨域的預檢機制
        /// </summary>
        public void  Options()
        {
        }
    }

  通過以上步驟就完成了跨域的預檢報文應答,接口簽名。實際運行效果如下圖所示:

  首先我發送一個非簡單報文的操作,我通過Fiddler實際抓到了兩個包,第一個就是上面所說的預檢報文,第二個纔是包含我們所有自定義報文頭部的實現我們真正跨域資源請求的報文。

  而預檢請求報文以及應答報文的信息如圖(格式也如上文所講):

  跨域資源請求報文及其應答報文如下(注意我們用於接口簽名的自定義報文頭):

  下面來一個正常通過跨域操作,並且簽名驗證失敗的報文,同樣是一個預檢報文,一個正式報文


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章