【Asp.Net】過濾器使用記錄


前言

在有一些所有請求都可能需要的操作時,我們可以添加過濾器來完成對請求的攔截,然后進行我們的操作,從而減少代碼的冗余。
關鍵字: Ajax 跨域 重定向 身份驗證攔截 異常攔截

問題

我們在對所有的請求攔截以后,沒有考慮 Url請求ajax請求 的區別,就對所有請求一致返回一個頁面(登錄頁、自定義錯誤頁等等),於是 ajax 得到的請求結果為 html ,這不是我們期望的

場景

一、身份驗證攔截,Ajax請求跨域重定向到頁面后不加載,場景分析

業務場景

目前項目需要使用公司的OAuth身份驗證平台做單點登錄(Single Sign On,SSO),於是要在目前的MVC項目中添加身份驗證相關的業務邏輯,項目中大量表格展示數據,使用了很多Ajax。我對項目大部分請求做了身份驗證,Ajax請求也做了攔截。

問題

對未登錄用戶發起的的Ajax請求,返回重定向的登錄頁,但是實際頁面並不會加載得到的登錄頁面。

分析

Ajax本就為頁面局部刷新修改而做的,返回的頁面只會被當做返回的普通數據。

在過濾器中身份驗證未通過后,請求被重定向(302)了項目的login方法,然后根據項目信息重定向(302)到認證平台的對應登錄頁面(200),瀏覽器攔截跨域請求資源,此時返回給Ajax的Http狀態碼為0(就是沒有返回狀態碼的情況)。於是,需要針對返回到Ajax的狀態碼為0時,做處理。
被攔截Ajax請求的情況

處理方法

通過強制刷新頁面,利用頁面請求攔截重新跳轉。

在全局js中添加Ajax的請求錯誤回調設置方法(還有其他方法ajaxComplete之類的,見W3CSchool)

/*ajax請求失敗回調默認方法*/
$(document).ajaxError(function (event, xhr, options, exc) {
    if (xhr.status === 0) {
        window.location.reload();    // 強制刷新頁面
    }
    else if (xhr.status === 302) {
        console.log(xhr);
    }
    else if (xhr.status === 401) {
        window.location.href = '/login';
        console.log(xhr);
    }
    else if (xhr.status === 404) {
        console.log(xhr);
    }
    else if (xhr.status === 500) {
        console.log(xhr);
    }
});

小結

這里暫時處理,返回狀態碼0的時候直接強制刷新頁面,然后對頁面刷新的身份驗證攔截重定向到登錄頁。
考慮修改,驗證請求類型為Ajax請求時,身份驗證失敗直接返回失敗響應狀態碼,並使用Js直接重定向到登錄頁面,省去無效重定向。

續:

使用Request.IsAjaxRequest();可以判斷請求類型,然后就方便處理了。可以對異步請求返回json對頁面請求返回視圖即可。

MVC身份驗證過濾器

類似下面的,過濾器主要進行身份驗證,跳轉SSO的認證平台及存儲獲取的令牌等操作由/login進行,不在此記錄。

using System.Web;
using System.Web.Mvc;

namespace Filter
{
    /// <summary>
    /// 身份驗證過濾器
    /// 使用方法:[CustAuthorize]特性
    /// 1.特性可以使用在Class和Method上,Class上表示內部所有方法需要驗證
    /// 2.可以Class上[CustAuthorize]並在不需要驗證的方法上[AllowAnonymous]
    /// </summary>
    public class CustAuthorizeAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// 請求的地址
        /// </summary>
        private string requestUrl { get; set; }

        /// <summary>
        /// 請求授權時執行
        /// </summary>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);   //進入AuthorizeCore
        }

        /// <summary>
        /// 自定義授權檢查(返回False則授權失敗)
        /// </summary>
        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext.Session["tk"] != null && !string.IsNullOrEmpty(httpContext.Session["tk"].ToString()))
            {
                string tk = httpContext.Session["tk"].ToString();    // 令牌,獲取到以后存在Session了,
                bool check = Check.check(tk);    // 驗證方法
                if (check)
                    return true;
            }
            httpContext.Session["tk"] = null;
            return false;     // 進入HandleUnauthorizedRequest 
        }

        /// <summary>
        /// 處理授權失敗的HTTP請求
        /// </summary>
        protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            if (filterContext.HttpContext.Request.IsAjaxRequest())  // ajax請求
            {
                filterContext.HttpContext.Response.StatusCode = 401;    //返回前端 js對此類返回狀態攔截響應或刷新頁面(轉頁面請求重定向至登錄)
                var json = JsonConvert.SerializeObject(new { state = "0", message = "權限驗證失敗!請重新登錄" });
                filterContext.HttpContext.Response.Write(json);
                filterContext.HttpContext.Response.End();
            }
            else // 未登錄請求頁面地址
            {
                filterContext.HttpContext.Session["requestUrl"] = filterContext.HttpContext.Request.Url.AbsolutePath;
                // 重定向到登錄
                filterContext.Result = new RedirectResult("~/login");
            }
        }
    }
}

登錄及回調方法

    public class OAuthController : Controller
    {
        [Route("login")]
        public ActionResult Login()
        {
            string redirect = "~/index";
                       
            // 未登錄
            if (string.IsNullOrEmpty(Session["tk"]?.ToString()))
            {
                // 獲取認證服務器頁面地址(請求地址+參數:回調地址等)
                redirect = SSOServer.GetLoginPageUrl(
                    $"http://{HttpContext.Request.Url?.Host}:{HttpContext.Request.Url?.Port}/loginCallBack");
            }

            return Redirect(redirect);
        }

        [Route("loginCallBack")]
        public ActionResult LoginCallBack(string tk)
        {
            string redirect = "~/index";

            if (string.IsNullOrEmpty(tk)) // 無令牌
            {
                redirect = "~/login";
            }
            else // 登錄返回令牌
            {
                UserInfo user = SSOServer.GetUserInfo(tk); // 認證服務器獲取用戶信息
                if (user == null || (user.userType != UserType.Admin))   // 角色限制
                {
                    SSOServer.Logout(tk); // 登出
                    Session.Clear();
                    redirect = "~/Error/Forbidden";    //自定義錯誤頁
                }
                else
                {
                    Session["tk"] = tk;
                    string requestUrl = Session["requestUrl"]?.ToString();    // 跳轉回登錄前頁面
                    Session["requestUrl"] = null;
                    if (!string.IsNullOrEmpty(requestUrl) && !requestUrl.Contains("login"))
                        redirect = requestUrl;
                }
            }

            return Redirect(redirect);
        }

        [Route("logout")]
        [CustAuthorize]
        public ActionResult Logout()
        {
            SSOServer.Logout(Session["tk"].ToString());
            Session.Abandon();
            return Redirect("~/login");
        }
    }

二、MVC自定義動態錯誤頁面后,Ajax錯誤回調方法接收不到對應狀態碼

關鍵字: Ajax http狀態碼

業務場景

MVC項目的錯誤頁面自定義(即通過控制器獲取Razor視圖而不是HTML靜態頁面)后,Ajax的請求接收到Http狀態碼只有200的情況。
(靜態的頁面,MVC可以在Web.config中配置狀態碼)

分析

我在Global.asax里做了簡單的500錯誤攔截,攔截后直接跳轉了自定義的錯誤頁面,動態重定向到指定控制器方法后,正常返回頁面,所以狀態碼是200。

處理方法

  1. 需要返回錯誤狀態
    Response.StatusCode = 500;
    控制器對應方法返回動態的錯誤頁面時,先修改響應狀態碼,然后Ajax就可以收到錯誤了,可以像上面一個問題里攔截回調處理,做提示一下之類的操作。

  2. 正確的異常攔截姿勢
    這里是目前我覺得比較正確的處理方法,參考asp.net MVC 過濾器使用案例:統一處理異常順道精簡代碼 —— 無恨星晨

using Utils.LogUtils;
using System;
using System.Web;
using System.Web.Mvc;

namespace Filter
{
    public class HandleExceptionAttribute: HandleErrorAttribute
    {
        public override void OnException(ExceptionContext ctx)
        {
            // 異常及路由信息
            Exception innerException = ctx.Exception;
            string controllerName = (string)ctx.RouteData.Values["controller"];
            string actionName = (string)ctx.RouteData.Values["action"];
            //Logger.ErrorLog(typeof(HandleExceptionAttribute), innerException,
                    "OnException捕獲:{0}->{1}:", controllerName, actionName);

            if ((new HttpException(null, innerException).GetHttpCode() == 500))
            {
                ActionResult result = null;
                if (!ctx.HttpContext.Request.IsAjaxRequest())    // 頁面請求
                {
                    HandleErrorInfo model = new HandleErrorInfo(innerException, controllerName, actionName);
                    result = new ViewResult
                    {
                        ViewName = "~/Views/Shared/Error.cshtml",
                        MasterName = this.Master,
                        ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
                        TempData = ctx.Controller.TempData
                    };
                }
                else    // 異步請求
                {
                    result = new JsonResult
                    {
                        Data = new Model.VO.MessageVO {
                            state = "error",
                            message = $"OnException捕獲:{controllerName}->{actionName}:{innerException}"
                        }
                    };
                }

                ctx.Result = result;
                ctx.ExceptionHandled = true;    // 避免再次進行默認異常處理
                ctx.HttpContext.Response.Clear();
                ctx.HttpContext.Response.StatusCode = 500;
                ctx.HttpContext.Response.TrySkipIisCustomErrors = true;
            }
            else   // 其他可能的異常,使用默認處理
            {
                base.OnException(ctx);
            }
        }
    }
}

總結

許久之后再看到這個問題,只感嘆當時的積累不足,沒有弄清楚過濾器的正確用法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM