.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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...