# Authorize 屬性和 Ajax Request 的愛恨情仇 ....
###### tags: `被玩壞了`
## 發生情境
### 使用 Ajax Call 後端時,遇到 Auth Cookie 已過期
按下搜尋後,發送 Ajax Request 請求,得到的 StatusCode 為302 (因為被重新導向至 Login 畫面)
也因為不是錯誤,Ajax 理所當然的將 Login 畫面丟至原本預留的位置...(如圖)

---
## 測試過程
### 首先,前端 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