# Authorize 屬性和 Ajax Request 的愛恨情仇 .... ###### tags: `被玩壞了` ## 發生情境 ### 使用 Ajax Call 後端時,遇到 Auth Cookie 已過期 按下搜尋後,發送 Ajax Request 請求,得到的 StatusCode 為302 (因為被重新導向至 Login 畫面) 也因為不是錯誤,Ajax 理所當然的將 Login 畫面丟至原本預留的位置...(如圖) ![](https://i.imgur.com/osDhndH.png) --- ## 測試過程 ### 首先,前端 Ajax 必須配置 error function,準備接收 401 狀態碼,重新導向 ``` $.ajax({ url: '@Url.Action("PagedPartial", "Member")', data: filter, type: 'Post', success: function (resultHtml) { // 取得資料後將目前 hash 重設 window.location.hash = page; // 將 PartialView 資料寫入 div id="MemberDatas" 的區塊 $('#MemberDatas').html(resultHtml); // 重設頁碼按鈕 $('#MemberDatas .pagination li a').each(function (i, item) { // 若是有超連結的頁碼 var hyperLinkUrl = $(item).attr('href'); if (typeof hyperLinkUrl !== 'undefined' && hyperLinkUrl !== false) { // 取得當前按鈕(<a>)的對應頁碼 var pageNumber = $(item).attr('href').replace('/Member?page=', ''); // 將頁碼按鈕的 href 去除 $(item).attr('href', '#'); // 設定按下頁碼事件 $(item).click(function (event) { event.preventDefault(); fetchPage(pageNumber); }); } }); }, error: function (xhr) { switch (xhr.status) { case 401: // 導至 Login 頁面,而且帶有 ReturnUrl window.location.href = "@Url.Action("Login","AdminUser", new { ReturnUrl = this.Context.Request.Path })" default: break; } } }); ``` ### 在 BaseController 中擷取 OnAuthorization 事件重新配置,遇到了什麼問題? * 如果配置 filterContext.Result,**一樣會變成 302**,有導向到 Login 畫面 * 設定 response.StatusCode = 401; response.End(); **會正常拋回 401,但會拋 Exception => 傳送 HTTP 標頭後,伺服器無法設定狀態。** ``` protected override void OnAuthorization(AuthorizationContext filterContext) { // 檢查 Action 有無 Auth 屬性 var isNeedAuth_Action = filterContext.ActionDescriptor.GetCustomAttributes(typeof(AuthorizeAttribute), true).Any(); // 檢查 Controller 有無 Auth 屬性 var isNeedAuth_Controller = filterContext.Controller.GetType().GetCustomAttributes(typeof(AuthorizeAttribute), true).Any(); // 是否需要驗證的網頁 var isNeedAuth = isNeedAuth_Action || isNeedAuth_Controller; var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; if (isNeedAuth && !filterContext.HttpContext.User.Identity.IsAuthenticated && request.IsAjaxRequest()) { // 如果配置 filterContext.Result,一樣會變成 302,有導向到 Login 畫面 //filterContext.Result = new RedirectResult($"/AdminUser/Login"); //filterContext.Result = new HttpUnauthorizedResult(); //filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Unauthorized, "未登入或登入逾時"); //↓測試之後沒用,一樣會出現 => 傳送 HTTP 標頭後,伺服器無法設定狀態。 //response.BufferOutput = true; //response.Clear(); // 可以正常回傳 401 沒錯,但會出現 => 傳送 HTTP 標頭後,伺服器無法設定狀態。 response.StatusCode = 401; response.End(); } else { base.OnAuthorization(filterContext); } } ``` #### 結論 不適用,也就不分析優缺點 ### 自定義 ActionFilter => AjaxAuthorizeAttribute,擷取 HandleUnauthorizedRequest ( 處理授權失敗的HTTP請求 ) ``` public class AjaxAuthorizeAttribute : AuthorizeAttribute { protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var request = httpContext.Request; var response = httpContext.Response; if (request.IsAjaxRequest()) { response.StatusCode = (int)HttpStatusCode.Unauthorized; response.End(); } else { base.HandleUnauthorizedRequest(filterContext); } } } ``` #### 結論 適用。 * 優點:搭配 Ajax 可以正常運作了 * 缺點:無法在 Controller 上加入 [Authorize] 屬性,需要個別加入在 Action 上 ( 若為 Ajax 呼叫的 Action 是使用 [AjaxAuthorize] ) ## 參考 https://dotblogs.com.tw/shadow/2014/05/04/144960