.NET MVC5簡介(四)Filter和AuthorizeAttribute權限驗證

在webform中,驗證的流程大體以下圖:html

 

 

 在AOP中:web

 

 

 在Filter中:編程

 

 

AuthorizeAttribute權限驗證 瀏覽器

登陸後有權限控制,有的頁面是須要用戶登陸才能訪問的,須要在訪問頁面增長一個驗證,也不能每一個action都一遍。緩存

一、寫一個CustomAuthorAttribute,繼承自AuthorizeAttribute,重寫OnAuthorization方法,在裏面把邏輯寫成本身的。服務器

二、有方法註冊和控制器註冊。ide

三、有全局註冊,所有控制器所有action都生效。函數

可是在這個裏面,首先要驗證登陸首頁,首頁沒有鄧麗,就跑到登陸頁面了,可是登陸頁面也要走特性裏面的邏輯,又重定向到鄧麗。。。循環了。。。。工具

這裏有一個AlloAnonymous,這個標籤就能夠解決這個循環的問題,匿名支持,不須要登陸就能夠,可是單單加特性是沒有用的,其實須要驗證時支持,甚至能夠說本身自定義一個特性也是能夠的,這個特性裏面是空的,只是爲了用來作標記。網站

特性的使用範圍,但願特性通用,在不一樣的系統,不一樣的地址登陸,==》在特性上面加個傳參的構造函數。

 public class CustomAllowAnonymousAttribute : Attribute
 {
 }

CustomAuthorAttribute類

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
    private Logger logger = new Logger(typeof(CustomAuthorizeAttribute));
    private string _LoginUrl = null;
    public CustomAuthorizeAttribute(string loginUrl = "~/Home/Login")
    {
        this._LoginUrl = loginUrl;
    }
    //public CustomAuthorizeAttribute(ICompanyUserService service)
    //{
    //}
    //不行


    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var httpContext = filterContext.HttpContext;//能拿到httpcontext 就能夠隨心所欲

        if (filterContext.ActionDescriptor.IsDefined(typeof(CustomAllowAnonymousAttribute), true))
        {
            return;
        }
        else if (filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(CustomAllowAnonymousAttribute), true))
        {
            return;
        }
        else if (httpContext.Session["CurrentUser"] == null
            || !(httpContext.Session["CurrentUser"] is CurrentUser))//爲空了,
        {
            //這裏有用戶,有地址 其實能夠檢查權限
            if (httpContext.Request.IsAjaxRequest())
                //httpContext.Request.Headers["xxx"].Equals("XMLHttpRequst")
            {
                filterContext.Result = new NewtonJsonResult(
                    new AjaxResult()
                    {
                        Result = DoResult.OverTime,
                        DebugMessage = "登錄過時",
                        RetValue = ""
                    });
            }
            else
            {
                httpContext.Session["CurrentUrl"] = httpContext.Request.Url.AbsoluteUri;
                filterContext.Result = new RedirectResult(this._LoginUrl);
                //短路器:指定了Result,那麼請求就截止了,不會執行action
            }
        }
        else
        {
            CurrentUser user = (CurrentUser)httpContext.Session["CurrentUser"];
            //this.logger.Info($"{user.Name}登錄了系統");
            return;//繼續
        }
        //base.OnAuthorization(filterContext);
    }
}

Filter生效機制

爲何加個標籤,繼承AuthorizeAttribute,重寫OnAuthorization方法就能夠了呢?控制器已經實例化,調用ExecuteCore方法,找到方法名字,ControllerActionInvokee.InvokeAction,找到所有的Filter特性,InvokeAuthorize--result不爲空,直接InvokeActionResult,爲空就正常執行Action。

有一個實例類型,有一個方法名稱,但願你反射執行

在找到方法後,執行方法前,能夠檢測下特性,來自全局的、來自控制器的、來自方法的。價差特性,特性是本身預約義的,按類執行,定個標識,爲空就正常,不爲空就跳轉,正常就繼續執行。

Filter原理和AOP面向切面編程

Filter是AOP思想的一種實現,其實就是ControllerActionInvoke這個類中,有個InvokeAction方法,控制器實例化以後,ActionInvoke先後,經過檢測預約義Filter而且執行它,達到AOP的目的。

下面是InvokeAction的源碼:

public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            if (string.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
            {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
            }
            ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext);
            ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);
            if (actionDescriptor != null)
            {
                FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);
                try
                {
                    AuthenticationContext authenticationContext = this.InvokeAuthenticationFilters(controllerContext, filters.AuthenticationFilters, actionDescriptor);
                    if (authenticationContext.Result != null)
                    {
                        AuthenticationChallengeContext authenticationChallengeContext = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authenticationContext.Result);
                        this.InvokeActionResult(controllerContext, authenticationChallengeContext.Result ?? authenticationContext.Result);
                    }
                    else
                    {
                        AuthorizationContext authorizationContext = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor);
                        if (authorizationContext.Result != null)
                        {
                            AuthenticationChallengeContext authenticationChallengeContext2 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authorizationContext.Result);
                            this.InvokeActionResult(controllerContext, authenticationChallengeContext2.Result ?? authorizationContext.Result);
                        }
                        else
                        {
                            if (controllerContext.Controller.ValidateRequest)
                            {
                                ControllerActionInvoker.ValidateRequest(controllerContext);
                            }
                            IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor);
                            ActionExecutedContext actionExecutedContext = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);
                            AuthenticationChallengeContext authenticationChallengeContext3 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, actionExecutedContext.Result);
                            this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, authenticationChallengeContext3.Result ?? actionExecutedContext.Result);
                        }
                    }
                }
                catch (ThreadAbortException)
                {
                    throw;
                }
                catch (Exception exception)
                {
                    ExceptionContext exceptionContext = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception);
                    if (!exceptionContext.ExceptionHandled)
                    {
                        throw;
                    }
                    this.InvokeActionResult(controllerContext, exceptionContext.Result);
                }
                return true;
            }
            return false;
        }
View Code

全局異常處理HandleErrorAttribute

關於異常處理的建議:

  一、避免UI層直接看到異常,每一個控制器裏面try-catch一下?不是很麻煩嗎?

  二、這個時候,AOP就登場了,HandleErrorAttribute,本身寫一個特性,繼承之HandleErrorAttribute,重寫OnException,在發生異常以後,會跳轉到這個方法。

在這邊,必定要

 public class CustomHandleErrorAttribute : HandleErrorAttribute
 {
     private Logger logger = new Logger(typeof(CustomHandleErrorAttribute));

     /// <summary>
     /// 會在異常發生後,跳轉到這個方法
     /// </summary>
     /// <param name="filterContext"></param>
     public override void OnException(ExceptionContext filterContext)
     {
         var httpContext = filterContext.HttpContext;//"隨心所欲"
         if (!filterContext.ExceptionHandled)//沒有被別的HandleErrorAttribute處理
         {
             this.logger.Error($"在響應 {httpContext.Request.Url.AbsoluteUri} 時出現異常,信息:{filterContext.Exception.Message}");//
             if (httpContext.Request.IsAjaxRequest())
             {
                 filterContext.Result = new NewtonJsonResult(
                 new AjaxResult()
                 {
                     Result = DoResult.Failed,
                     DebugMessage = filterContext.Exception.Message,
                     RetValue = "",
                     PromptMsg = "發生錯誤,請聯繫管理員"
                 });
             }
             else
             {
                 filterContext.Result = new ViewResult()//短路器
                 {
                     ViewName = "~/Views/Shared/Error.cshtml",
                     ViewData = new ViewDataDictionary<string>(filterContext.Exception.Message)
                 };
             }
             filterContext.ExceptionHandled = true;//已經被我處理了
         }
     }
 }

這個是要從新跳轉的地址:

 

 

 必定要考慮到是否是Ajax請求的

 

 

 

 

 

 多種異常狀況,能不能進入自定義的異常呢?

一、Action異常,沒有被Catch

二、Action異常,被Catch

三、Action調用Service異常

四、Action正常視圖出現異常了

五、控制器構造出現異常

六、Action名稱錯誤

七、任意地址錯誤

八、權限Filter異常

答案:

一、能夠

二、不能夠

三、能夠,異常冒泡

四、能夠,爲何呢?由於ExecuteResult是包裹在try裏面的

五、不能夠的,Filter是在構造完成控制以後方法執行以前完成的

六、不能夠的,由於請求都沒進MVC流程

七、不能夠的,由於請求都沒進MVC

八、能夠的,權限Filter也是在try裏面的。

那這些沒有被捕獲的異常怎麼辦?還有一個方法

在Global中增長一個事件

 public class MvcApplication : System.Web.HttpApplication
 {
     private Logger logger = new Logger(typeof(MvcApplication));
     protected void Application_Start()
     {
         AreaRegistration.RegisterAllAreas();//註冊區域
         FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);//註冊全局的Filter
         RouteConfig.RegisterRoutes(RouteTable.Routes);//註冊路由
         BundleConfig.RegisterBundles(BundleTable.Bundles);//合併壓縮 ,打包工具 Combres
         ControllerBuilder.Current.SetControllerFactory(new ElevenControllerFactory());

         this.logger.Info("網站啓動了。。。");
     }
     /// <summary>
     /// 全局式的異常處理,能夠抓住漏網之魚
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     protected void Application_Error(object sender, EventArgs e)
     {
         Exception excetion = Server.GetLastError();
         this.logger.Error($"{base.Context.Request.Url.AbsoluteUri}出現異常");
         Response.Write("System is Error....");
         Server.ClearError();

         //Response.Redirect
         //base.Context.RewritePath("/Home/Error?msg=")
     }

HandleErrorAttribute+Application_Error,粒度不同,能拿到的東西不同

IActionFilter擴展定製

IActionFilter

一、OnActionExecuting   方法執行前

二、OnActionExecuted方法執行後

三、OnResultExecuting結果執行前

四、OnResultExecuted結果執行後

先執行權限Filter,再執行ActionFilter。

執行的順序:

  Global OnActionExecuting

  Controller OnActionExecuting

  Action OnActionExecuting

  Action真實執行

  Action OnActionExecuted

  Controller OnActionExecuted

  Global OnActionExecuted

 

 

不一樣位置註冊的生效順序:全局---》控制器-----》Action

好像一個俄羅斯套娃,或者說洋蔥模型

 

 

在同一個位置註冊的生效順序,同一個位置按照前後順序生效,還有一個Order的參數,不設置Order默認是1,設置以後按照從小到大執行

 

 

 

ActionFilter能幹什麼?

日誌、參數檢測、緩存、重寫視圖、壓縮、防盜鏈、統計訪問、不一樣的客戶端跳轉不一樣的頁面、限流.....

瀏覽器請求時,會聲明支持的格式,默認的IIS是沒有壓縮的,檢測了支持的格式,在響應時將數據壓縮(IIS服務器完成的),在響應頭裏面加上Content-Encoding,瀏覽器查看數據格式,按照瀏覽器格式解壓(不管你是什麼東西,均可以壓縮解壓的),壓縮是IIS,解壓是瀏覽器的。

 public class CompressActionFilterAttribute : ActionFilterAttribute
 {
     public override void OnActionExecuting(ActionExecutingContext filterContext)
     {
         //foreach (var item in filterContext.ActionParameters)
         //{
         //    //參數檢測  敏感詞過濾
         //}  
         var request = filterContext.HttpContext.Request;
         var respose = filterContext.HttpContext.Response;
         string acceptEncoding = request.Headers["Accept-Encoding"];//檢測支持格式
         if (!string.IsNullOrWhiteSpace(acceptEncoding) && acceptEncoding.ToUpper().Contains("GZIP"))
         {
             respose.AddHeader("Content-Encoding", "gzip");//響應頭指定類型
             respose.Filter = new GZipStream(respose.Filter, CompressionMode.Compress);//壓縮類型指定
         }
     }
 }

 public class LimitActionFilterAttribute : ActionFilterAttribute
 {
     private int _Max = 0;
     public LimitActionFilterAttribute(int max = 1000)
     {
         this._Max = max;
     }
     public override void OnActionExecuting(ActionExecutingContext filterContext)
     {
         string key = $"{filterContext.RouteData.Values["Controller"]}_{filterContext.RouteData.Values["Action"]}";
         //CacheManager.Add(key,) 存到緩存key 集合 時間  
         filterContext.Result = new JsonResult()
         {
             Data = new { Msg = "超出頻率" }
         };
     }
 }

 

 

 

 

 

 

 Filter這麼厲害,有沒有什麼侷限性????

雖然很豐富,可是隻能以Action爲單位,Action內部調用別的類庫,加操做就作不到!這種就得靠IOC+AOP擴展。

本篇只是介紹了.NET Framework MVC 中的過濾器Filter(權限特性、Action、Result、Exception),其實在.NET Core MVC 增長了ResourceFilter,加了這個特性,資源特性,Action/Result /Exception三個特性沒有什麼變化。後面記錄到到.NET Core MVC時再詳細介紹。

相關文章
相關標籤/搜索