聊一聊Asp.net過濾器Filter那一些事

 更新時間:2020-06-14 00:02:00   作者:佚名   我要評論(0)

最近在整理優化.net代碼時,發現幾個很不友好的處理現象:登錄判斷、權限認證、日志記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優化上,這一點是

最近在整理優化.net代碼時,發現幾個很不友好的處理現象:登錄判斷、權限認證、日志記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優化上,這一點是很重要著力點。這時.net中的過濾器、攔截器(Filter)就派上用場了。現在根據這幾天的實際工作,對其做了一個簡單的梳理,分享出來,以供大家參考交流,如有寫的不妥之處,多多指出,多多交流。

概述:

.net中的Filter中主要包括以下4大類:Authorize(授權),ActionFilter(自定義),HandleError(錯誤處理)。

過濾器

類名

實現接口

描述

授權

AuthorizeAttribute

IAuthorizationFilter

此類型(或過濾器)用于限制進入控制器或控制器的某個行為方法,比如:登錄、權限、訪問控制等等

異常

HandleErrorAttribute

IExceptionFilter

用于指定一個行為,這個被指定的行為處理某個行為方法或某個控制器里面拋出的異常,比如:全局異常統一處理。

自定義

ActionFilterAttribute

IActionFilter和IResultFilter

用于進入行為之前或之后的處理或返回結果的之前或之后的處理,比如:用戶請求日志詳情日志記錄

AuthorizeAttribute:認證授權

認證授權主要是對所有action的訪問第一入口認證,對用戶的訪問做第一道監管過濾攔截閘口。

實現方式:需要自定義一個類,繼承AuthorizeAttribute并重寫OnAuthorization,在OnAuthorization中能夠獲取到用戶請求的所有Request信息,其實我們做的所有認證攔截操作,其所有數據支撐都是來自Request中。

具體驗證流程設計:

IP白名單:這個主要針對的是API做IP限制,只有指定IP才可訪問,非指定IP直接返回

請求頻率控制:這個主要是控制用戶的訪問頻率,主要是針對API做,超出請求頻率直接返回。

登錄認證:登錄認證一般我們采用的是通過在請求的header中傳遞token的方式來進行驗證,這樣即使用與一般的MVC登錄認證,也使用與API接口的Auth認證,并且也不依賴于用戶前端js設置等。

授權認證:授權認證就簡單了,主要是驗證該用戶是否具有該權限,如果不具有,直接做下相應的返回處理。

MVC和API異同:

  命名空間:MVC:System.Web.Http.Filters;API:System.Web.Mvc

  注入方式:在注入方式上,主要包括:全局->控制器Controller->行為Action

  全局注冊:針對所有系統的所有Aciton都使用

  Controller:只針對該Controller下的Action起作用

  Action:只針對該Action起作用

其中全局注冊,針對MVC和API還有一些差異:

  MVC在 FilterConfig.cs中注入

filters.Add(new XYHMVCAuthorizeAttribute());

     API 在 WebApiConfig.cs 中注入

config.Filters.Add(new XYHAPIAuthorizeAttribute());

  注意事項:在實際使用中,針對認證授權,我們一般都是添加全局認證,但是,有的action又不需要做認證,比如本來的登錄Action等等,那么該如何排除呢?其實也很簡單,我們只需要在自定定義一個Attribute集成Attribute,或者系統的AllowAnonymousAttribute,在不需要驗證的action中只需要注冊上對于的Attribute,并在驗證前做一個過濾即可,比如:

// 有 AllowAnonymous 屬性的接口直接開綠燈
   if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
   {
    return;
   }

API AuthFilterAttribute實例代碼

/// <summary>
 /// 授權認證過濾器
 /// </summary>
 public class XYHAPIAuthFilterAttribute : AuthorizationFilterAttribute
 {
  /// <summary>
  /// 認證授權驗證
  /// </summary>
  /// <param name="actionContext">請求上下文</param>
  public override void OnAuthorization(HttpActionContext actionContext)
  {
   // 有 AllowAnonymous 屬性的接口直接開綠燈
   if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
   {
    return;
   }

   // 在請求前做一層攔截,主要驗證token的有效性和驗簽
   HttpRequest httpRequest = HttpContext.Current.Request;

   // 獲取apikey
   var apikey = httpRequest.QueryString["apikey"];

   // 首先做IP白名單校驗 
   MBaseResult<string> result = new AuthCheckService().CheckIpWhitelist(FilterAttributeHelp.GetIPAddress(actionContext.Request), apikey);

   // 檢驗時間搓
   string timestamp = httpRequest.QueryString["Timestamp"];
   if (result.Code == MResultCodeEnum.successCode)
   {
    // 檢驗時間搓 
    result = new AuthCheckService().CheckTimestamp(timestamp);
   }

   if (result.Code == MResultCodeEnum.successCode)
   {
    // 做請求頻率驗證 
    string acitonName = actionContext.ActionDescriptor.ActionName;
    string controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
    result = new AuthCheckService().CheckRequestFrequency(apikey, $"api/{controllerName.ToLower()}/{acitonName.ToLower()}");
   }

   if (result.Code == MResultCodeEnum.successCode)
   {
    // 簽名校驗

    // 獲取全部的請求參數
    Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

    result = new AuthCheckService().SignCheck(queryParameters, apikey);

    if (result.Code == MResultCodeEnum.successCode)
    {
     // 如果有NoChekokenFilterAttribute 標簽 那么直接不做token認證
     if (actionContext.ActionDescriptor.GetCustomAttributes<XYHAPINoChekokenFilterAttribute>().Any())
     {
      return;
     }

     // 校驗token的有效性
     // 獲取一個 token
     string token = httpRequest.Headers.GetValues("Token") == null ? string.Empty :
      httpRequest.Headers.GetValues("Token")[0];

     result = new AuthCheckService().CheckToken(token, apikey, httpRequest.FilePath);
    }
   }

   // 輸出
   if (result.Code != MResultCodeEnum.successCode)
   {
    // 一定要實例化一個response,是否最終還是會執行action中的代碼
    actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
    //需要自己指定輸出內容和類型
    HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
    HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
    HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統
   }
  }
 }

 MVC AuthFilterAttribute實例代碼

/// <summary>
 /// MVC自定義授權
 /// 認證授權有兩個重寫方法
 /// 具體的認證邏輯實現:AuthorizeCore 這個里面寫具體的認證邏輯,認證成功返回true,反之返回false
 /// 認證失敗處理邏輯:HandleUnauthorizedRequest 前一步返回 false時,就會執行到該方法中
 /// 但是,我平時在應用過程中,一般都是在AuthorizeCore根據不同的認證結果,直接做認證后的邏輯處理
 /// </summary>
 public class XYHMVCAuthorizeAttribute : AuthorizeAttribute
 {
  /// <summary>
  /// 認證邏輯
  /// </summary>
  /// <param name="filterContext">過濾器上下文</param>
  public override void OnAuthorization(AuthorizationContext filterContext)
  {

   // 此處主要寫認證授權的相關驗證邏輯
   // 該部分的驗證一般包括兩個部分
   // 登錄權限校驗
   // --我們的一般處理方式是,通過header中傳遞一個token來進行邏輯驗證
   // --當然不同的系統在設計上也不盡相同,有的也會采用session等方式來驗證
   // --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作

   // 具體的頁面權限校驗
   // --該部分的驗證是具體的到頁面權限驗證
   // --我看有得小伙伴沒有做到這一個程度,直接將這一步放在前端js來驗證,這樣不是很安全,但是可以攔住小白用戶
   // --當然有的系統根本就沒有做權限控制,那就更不需要這一個邏輯了。
   // --所以最終還是根據其項目本身的實際情況來進行對應的邏輯操作

   // 現在用一個粗暴的方式來簡單模擬實現過,用系統當前時間段秒廚藝3,取余數
   // 當余數為0:認證授權通過
   //   1:代表為登錄,調整至登錄頁面
   //   2:代表無訪問權限,調整至無權限提示頁面

   // 當然,在這也還可以做一些IP白名單,IP黑名單驗證 請求頻率驗證等等

   // 說到這而,還有一點需要注意,如果我們選擇的是全局注冊該過濾器,那么如果有的頁面根本不需要權限認證,比如登錄頁面,那么我們可以給不需要權限的認證的控制器或者action添加一個特殊的注解 AllowAnonymous ,來排除

   // 獲取Request的幾個關鍵信息
   HttpRequest httpRequest = HttpContext.Current.Request;
   string acitonName = filterContext.ActionDescriptor.ActionName;
   string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;

   // 注意:如果認證不通過,需要設置filterContext.Result的值,否則還是會執行action中的邏輯

   filterContext.Result = null;
   int thisSecond = System.DateTime.Now.Second;
   switch (thisSecond % 3)
   {
    case 0:
     // 認證授權通過
     break;
    case 1:
     // 代表為登錄,調整至登錄頁面
     // 只有設置了Result才會終結操作
     filterContext.Result = new RedirectResult("/html/Login.html");
     break;
    case 2:
     // 代表無訪問權限,調整至無權限提示頁面
     filterContext.Result = new RedirectResult("/html/NoAuth.html");
     break;
   }
  }
 }

ActionFilter:自定義過濾器

自定義過濾器,主要是監控action請求前后,處理結果返回前后的事件。其中API只有請求前后的兩個方法。

重新方法

方法功能描述

使用于

OnActionExecuting

一個請求在進入到aciton邏輯前執行

MVC、API

OnActionExecuted

一個請求aciton邏輯執行后執行

MVC、API

OnResultExecuting

對應的view視圖渲染前執行

MVC

OnResultExecuted

對應的view視圖渲染后執行

MVC

在這幾個方法中,我們一般主要用來記錄交互日志,記錄每一個步驟的耗時情況,以便后續系統優化使用。具體的使用,根據自身的業務場景使用。

其中MVC和API的異同點,和上面說的認證授權的異同類似,不在詳細說明。

下面的一個實例代碼:

API定義過濾器實例DEMO代碼

/// <summary>
 /// Action過濾器
 /// </summary>
 public class XYHAPICustomActionFilterAttribute : ActionFilterAttribute
 {
  /// <summary>
  /// Action執行開始
  /// </summary>
  /// <param name="actionContext"></param>
  public override void OnActionExecuting(HttpActionContext actionContext)
  {

  }

  /// <summary>
  /// action執行以后
  /// </summary>
  /// <param name="actionContext"></param>
  public override void OnActionExecuted(HttpActionExecutedContext actionContext)
  {
   try
   {
    // 構建一個日志數據模型
    MApiRequestLogs apiRequestLogsM = new MApiRequestLogs();

    // API名稱
    apiRequestLogsM.API = actionContext.Request.RequestUri.AbsolutePath;

    // apiKey
    apiRequestLogsM.API_KEY = HttpContext.Current.Request.QueryString["ApiKey"];

    // IP地址
    apiRequestLogsM.IP = FilterAttributeHelp.GetIPAddress(actionContext.Request);

    // 獲取token
    string token = HttpContext.Current.Request.Headers.GetValues("Token") == null ? string.Empty :
        HttpContext.Current.Request.Headers.GetValues("Token")[0];
    apiRequestLogsM.TOKEN = token;

    // URL
    apiRequestLogsM.URL = actionContext.Request.RequestUri.AbsoluteUri;

    // 返回信息
    var objectContent = actionContext.Response.Content as ObjectContent;
    var returnValue = objectContent.Value;
    apiRequestLogsM.RESPONSE_INFOR = returnValue.ToString();

    // 由于數據庫中最大只能存儲4000字符串,所以對返回值做一個截取
    if (!string.IsNullOrEmpty(apiRequestLogsM.RESPONSE_INFOR) &&
     apiRequestLogsM.RESPONSE_INFOR.Length > 4000)
    {
     apiRequestLogsM.RESPONSE_INFOR = apiRequestLogsM.RESPONSE_INFOR.Substring(0, 2000);
    }

    // 請求參數
    apiRequestLogsM.REQUEST_INFOR = actionContext.Request.RequestUri.Query;

    // 定義一個異步委托 ,異步記錄日志
    // Func<MApiRequestLogs, string> action = AddApiRequestLogs;//聲明一個委托
    // IAsyncResult ret = action.BeginInvoke(apiRequestLogsM, null, null);

   }
   catch (Exception ex)
   {

   }
  }
 }

HandleError:錯誤處理

異常處理對于我們來說很常用,很好的利用異常處理,可以很好的避免全篇的try/catch。異常處理箱單很簡單,值需要自定義集成:ExceptionFilterAttribute,并自定義實現:OnException方法即可。

在OnException我們可以根據自身需要,做一些相應的邏輯處理,比如記錄異常日志,便于后續問題分析跟進。

OnException還有一個很重要的處理,那就是對異常結果的統一包裝,返回一個很友好的結果給用戶,避免把一些不必要的信息返回給用戶。比如:針對MVC,那么跟進不同異常,統一調整至友好的提示頁面等等;針對API,那么我們可以一個統一的返回幾個封裝,便于用戶統一處理結果。

MVC 的異常處理實例代碼:

 /// <summary>
 /// MVC自定義異常處理機制
 /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作
 /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常
 /// 該類就發揮了作用,能夠很好的未經捕獲的異常,并做相應的邏輯處理
 /// 自定義異常機制,主要集成HandleErrorAttribute 重寫其OnException方法
 /// </summary>
 public class XYHMVCHandleError : HandleErrorAttribute
 {
  /// <summary>
  /// 處理異常
  /// </summary>
  /// <param name="filterContext">異常上下文</param>
  public override void OnException(ExceptionContext filterContext)
  {
   // 我們在平時的項目中,異常處理一般有兩個作用
   // 1:記錄異常的詳細日志,便于事后分析日志
   // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面

   // 在這里面既能獲取到未經處理的異常信息,也能獲取到請求信息
   // 在此可以根據實際項目需要做相應的邏輯處理
   // 下面簡單的列舉了幾個關鍵信息獲取方式

   // 控制器名稱 注意,這樣獲取出來的是一個文件的全路徑 
   string contropath = filterContext.Controller.ToString();

   // 訪問目錄的相對路徑
   string filePath = filterContext.HttpContext.Request.FilePath;

   // url完整地址
   string url = (filterContext.HttpContext.Request.Url.AbsoluteUri).ExUrlDeCode();

   // 請求方式 post get
   string httpMethod = filterContext.HttpContext.Request.HttpMethod;

   // 請求IP地址
   string ip = filterContext.HttpContext.Request.GetIPAddress();

   // 獲取全部的請求參數
   HttpRequest httpRequest = HttpContext.Current.Request;
   Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

   // 獲取異常對象
   Exception ex = filterContext.Exception;

   // 異常描述信息
   string exMessage = ex.Message;

   // 異常堆棧信息
   string stackTrace = ex.StackTrace;

   // 根據實際情況記錄日志(文本日志、數據庫日志,建議具體步驟采用異步方式來完成)


   filterContext.ExceptionHandled = true;

   // 模擬根據不同的做對應的邏輯處理
   int statusCode = filterContext.HttpContext.Response.StatusCode;

   if (statusCode>=400 && statusCode<500)
   {
    filterContext.Result = new RedirectResult("/html/404.html");
   }
   else 
   {
    filterContext.Result = new RedirectResult("/html/500.html");
   }
  }
 }

API 的異常處理實例代碼:

 /// <summary>
 /// API自定義異常處理機制
 /// 說道異常處理,其實我們腦海中的第一反應,也該是try/cache操作
 /// 但是在實際開發中,很有可能地址錯誤根本就進入不到try中,又或者沒有被try處理到異常
 /// 該類就發揮了作用,能夠很好的未經捕獲的異常,并做相應的邏輯處理
 /// 自定義異常機制,主要集成ExceptionFilterAttribute 重寫其OnException方法
 /// </summary>
 public class XYHAPIHandleError : ExceptionFilterAttribute
 {
  /// <summary>
  /// 處理異常
  /// </summary>
  /// <param name="actionExecutedContext">異常上下文</param>
  public override void OnException(HttpActionExecutedContext actionExecutedContext)
  {
   // 我們在平時的項目中,異常處理一般有兩個作用
   // 1:記錄異常的詳細日志,便于事后分析日志
   // 2:對異常的統一友好處理,比如根據異常類型重定向到友好提示頁面

   // 在這里面既能獲取到未經處理的異常信息,也能獲取到請求信息
   // 在此可以根據實際項目需要做相應的邏輯處理
   // 下面簡單的列舉了幾個關鍵信息獲取方式

   // action名稱 
   string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;

   // 控制器名稱 
   string controllerName =actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName;

   // url完整地址
   string url = (actionExecutedContext.Request.RequestUri.AbsoluteUri).ExUrlDeCode();

   // 請求方式 post get
   string httpMethod = actionExecutedContext.Request.Method.Method;

   // 請求IP地址
   string ip = actionExecutedContext.Request.GetIPAddress();

   // 獲取全部的請求參數
   HttpRequest httpRequest = HttpContext.Current.Request;
   Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();

   // 獲取異常對象
   Exception ex = actionExecutedContext.Exception;

   // 異常描述信息
   string exMessage = ex.Message;

   // 異常堆棧信息
   string stackTrace = ex.StackTrace;

   // 根據實際情況記錄日志(文本日志、數據庫日志,建議具體步驟采用異步方式來完成)
   // 自己的記錄日志落地邏輯略 ......

   // 構建統一的內部異常處理機制,相當于對異常做一層統一包裝暴露
   MBaseResult<string> result = new MBaseResult<string>()
   {
    Code = MResultCodeEnum.systemErrorCode,
    Message = MResultCodeEnum.systemError
   };

   actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
   //需要自己指定輸出內容和類型
   HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
   HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
   HttpContext.Current.Response.End(); // 此處結束響應,就不會走路由系統
  }
 }

總結

.net過濾器,我個人的一句話理解就是:對action的各個階段進行統一的監控處理等操作。.net過濾器中,其中每一個種過濾器的執行先后順序為:Authorize(授權)-->ActionFilter(自定義)-->HandleError(錯誤處理)

好了,就先聊到這而,如果什么地方說的不對之處,多多指點和多多包涵。我自己寫了一個練習DEMO,里面會有每一種情況的處理說明。有興趣的可以取下載下來看一看,謝謝。

DEMO在GitHub地址為:https://github.com/xuyuanhong0902/XYH.FilterTest.git

到此這篇關于聊一聊Asp.net過濾器Filter那一些事的文章就介紹到這了,更多相關Asp.net過濾器Filter內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

  • asp.net core MVC 過濾器之ActionFilter過濾器(2)
  • asp.net core MVC 全局過濾器之ExceptionFilter過濾器(1)

相關文章

  • asp.net mvc core管道及攔截器的理解

    asp.net mvc core管道及攔截器的理解

    今天來看一下asp.net core的執行管道。先看下官方說明: 從上圖可以拋光,asp.net core的執行順序是,當收到一個請求后,request請求會先經過已注冊的中間件,然后
    2020-06-14
  • 聊一聊Asp.net過濾器Filter那一些事

    聊一聊Asp.net過濾器Filter那一些事

    最近在整理優化.net代碼時,發現幾個很不友好的處理現象:登錄判斷、權限認證、日志記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優化上,這一點是
    2020-06-14
  • xUnit 編寫 ASP.NET Core 單元測試的方法

    xUnit 編寫 ASP.NET Core 單元測試的方法

    還記得 .NET Framework 的 ASP.NET WebForm 嗎?那個年代如果要在 Web 層做單元測試簡直就是災難啊。.NET Core 吸取教訓,在設計上考慮到了可測試性,就連 ASP.NET
    2020-06-14
  • ASP.NET Core自定義中間件如何讀取Request.Body與Response.Body的內容詳解

    ASP.NET Core自定義中間件如何讀取Request.Body與Response.Body的內容詳解

    背景# 最近在徒手造輪子,編寫一個ASP.NET Core的日志監控器,其中用到了自定義中間件讀取Request.Body和Response.Body的內容,但是編寫過程,并不像想象中的一帆
    2020-06-14
  • ASP.NET Core MVC如何實現運行時動態定義Controller類型

    ASP.NET Core MVC如何實現運行時動態定義Controller類型

    昨天有個朋友在微信上問我一個問題:他希望通過動態腳本的形式實現對ASP.NET Core MVC應用的擴展,比如在程序運行過程中上傳一段C#腳本將其中定義的Controller類型注
    2020-06-14
  • 實例講解PHP表單

    實例講解PHP表單

    表單處理 GET vs. POST 1 GET 和 POST 都創建數組(例如,array( key => value, key2 => value2, key3 => value3, ...))。此數組包含鍵/值對,其中的鍵是表單控
    2020-06-10
  • keras多顯卡訓練方式

    keras多顯卡訓練方式

    使用keras進行訓練,默認使用單顯卡,即使設置了os.environ['CUDA_VISIBLE_DEVICES']為兩張顯卡,也只是占滿了顯存,再設置tf.GPUOptions(allow_growth=True)之后可
    2020-06-10
  • 淺談Python中的模塊

    淺談Python中的模塊

    模塊 為了編寫可維護的代碼,我們把很多函數分組,分別放到不同的文件里,這樣,每個文件包含的代碼就相對較少,很多編程語言都采用這種組織代碼的方式。在Pytho
    2020-06-10
  • 基于SQLAlchemy實現操作MySQL并執行原生sql語句

    基于SQLAlchemy實現操作MySQL并執行原生sql語句

    場景應用 老大我讓爬取內部網站獲取數據,插入到新建的表中,并每天進行爬取更新數據(后面做了定時任務)。然后根據該表統計每日的新增數量/更新數量進行制圖制
    2020-06-10
  • 什么是python類屬性

    什么是python類屬性

    首先我們來看一下屬性的定義 屬性的定義:python中的屬性其實是普通方法的衍生。 操作類屬性有三種方法: 1.使用@property裝飾器操作類屬性。 2.使用類或實例直接操
    2020-06-10

最新評論

买宝宝用品赚钱吗 上海快3开奖l结果近50期 四肖心水 在线 模拟炒股app排行 江苏快三综合性走势图 2003年上证指数 海南4+1开奖视频 明天大盘走势预测 群英会今日预测 福彩3d重邻间孤走势图 福彩快3开奖号码湖北 山西体彩11选5前3直走势图 2008-2018年上证指数图 河南22选五大星走势图 广西快3下载app下载安装 秒速快三怎稳赢 虚拟股票游戏