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

来源:https://www.cnblogs.com/taotaozhuanyong/archive/2019/09/23/11575590.html
-Advertisement-
Play Games

在webform中,驗證的流程大致如下圖: 在AOP中: 在Filter中: AuthorizeAttribute許可權驗證 登錄後有許可權控制,有的頁面是需要用戶登錄才能訪問的,需要在訪問頁面增加一個驗證,也不能每個action都一遍。 1、寫一個CustomAuthorAttribute,繼承自Au ...


在webform中,驗證的流程大致如下圖:

 

 

 在AOP中:

 

 

 在Filter中:

 

 

AuthorizeAttribute許可權驗證 

登錄後有許可權控制,有的頁面是需要用戶登錄才能訪問的,需要在訪問頁面增加一個驗證,也不能每個action都一遍。

1、寫一個CustomAuthorAttribute,繼承自AuthorizeAttribute,重寫OnAuthorization方法,在裡面把邏輯寫成自己的。

2、有方法註冊和控制器註冊。

3、有全局註冊,全部控制器全部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

關於異常處理的建議:

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

  2、這個時候,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請求的

 

 

 

 

 

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

1、Action異常,沒有被Catch

2、Action異常,被Catch

3、Action調用Service異常

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

5、控制器構造出現異常

6、Action名稱錯誤

7、任意地址錯誤

8、許可權Filter異常

答案:

1、可以

2、不可以

3、可以,異常冒泡

4、可以,為什麼呢?因為ExecuteResult是包裹在try裡面的

5、不可以的,Filter是在構造完成控制之後方法執行之前完成的

6、不可以的,因為請求都沒進MVC流程

7、不可以的,因為請求都沒進MVC

8、可以的,許可權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

1、OnActionExecuting   方法執行前

2、OnActionExecuted方法執行後

3、OnResultExecuting結果執行前

4、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時再詳細介紹。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、題目 二、思路 1、dfs 實驗要求用多種思路完成,所以一開始就沿用了上一個實驗馬走棋盤的思路,添加了鄰接矩陣來記錄有向網的權值。總體思路還是DFS遍歷搜索。 過程剪枝: 1、因為要求為最短路徑,而一般情況總會存在多條可行路徑,在判斷過程中需要走過每一條路徑才能知道該路徑的長度,但如果已知一條可 ...
  • [TOC] 閉包函數 什麼是閉包函數 閉包函數把 閉包函數內的變數 + 閉包函數內部的函數, 這兩者包裹起來,然後通過返回值的形式返回出來。 定義在函數的內函數 該函數體代碼包含對該函數外層作用域中變數的引用 函數外層指的不是全局作用域 上述代碼中,f是一個全局的名字,但f拿到了inner的記憶體地址 ...
  • 我是一個2019畢業的非電腦的畢業生,從大二開始喜歡上Java直到現在一直都在學習,Brid從小就對電腦感興趣,可惜高中的時候不懂事,沒有規劃未來,考上了一所專科學院,然後大一併不能轉專業,現在畢業了沒有找到Java應屆的工作,只能找點其他的做,但是這阻住不了我對Java的喜歡,趁現在工作的晚上 ...
  • “容器”這兩個字很少被 Python 技術文章提起。一看到“容器”,大家想到的多是那頭藍色小鯨魚:Docker,但這篇文章和它沒有任何關係。本文里的容器,是 Python 中的一個抽象概念,是對專門用來裝其他對象的數據類型的統稱。 在 Python 中,有四類最常見的內建容器類型: 列表(list) ...
  • 溫馨提示 請收藏再看。此文篇幅太長,你短時間看不完;此文乾貨太多,錯過太可惜。 示例代碼可以關註 (公眾號)回覆 獲取。 收穫 1. 講解詳細:能讓你掌握使用 及類似校驗工具的各種使用姿勢 2. 內容全面:可以當做知識字典來查詢 what 註意:hibernate validator 與 持久層框架 ...
  • 閑及無聊 又打開了CSDN開始看一看有什麼先進的可以學習的相關帖子,這時看到了一位大神寫的簡歷裝X必備,手寫Spring MVC。 我想這個東西還是有一點意思的 就拜讀了一下大佬的博客 通讀了一遍相關代碼 感覺和我想象中spring的運作流程基本相同 但是我腦海中基本上只有一個非常簡單的基本概念 而 ...
  • 多好,多簡單,多好 ...
  • 一、題目 設平面上分佈著n個白點和n個黑點,每個點用一對坐標(x, y)表示。一個黑點b=(xb,yb)支配一個白點w=(xw, yw)當且僅當xb>=xw和yb>=yw。 若黑點b支配白點w,則黑點b和白點w可匹配(可形成一個匹配對)。 在一個黑點最多只能與一個白點匹配,一個白點最多只能與一個黑點 ...
一周排行
    -Advertisement-
    Play Games
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...