聊一聊Asp.net過濾器Filter那一些事
最近在整理優(yōu)化.net代碼時,發(fā)現(xiàn)幾個很不友好的處理現(xiàn)象:登錄判斷、權(quán)限認(rèn)證、日志記錄、異常處理等通用操作,在項目中的action中到處都是。在代碼優(yōu)化上,這一點是很重要著力點。這時.net中的過濾器、攔截器(Filter)就派上用場了?,F(xiàn)在根據(jù)這幾天的實際工作,對其做了一個簡單的梳理,分享出來,以供大家參考交流,如有寫的不妥之處,多多指出,多多交流。
概述:
.net中的Filter中主要包括以下4大類:Authorize(授權(quán)),ActionFilter(自定義),HandleError(錯誤處理)。
|
過濾器 |
類名 |
實現(xiàn)接口 |
描述 |
|
授權(quán) |
AuthorizeAttribute |
IAuthorizationFilter |
此類型(或過濾器)用于限制進(jìn)入控制器或控制器的某個行為方法,比如:登錄、權(quán)限、訪問控制等等 |
|
異常 |
HandleErrorAttribute |
IExceptionFilter |
用于指定一個行為,這個被指定的行為處理某個行為方法或某個控制器里面拋出的異常,比如:全局異常統(tǒng)一處理。 |
|
自定義 |
ActionFilterAttribute |
IActionFilter和IResultFilter |
用于進(jìn)入行為之前或之后的處理或返回結(jié)果的之前或之后的處理,比如:用戶請求日志詳情日志記錄 |
AuthorizeAttribute:認(rèn)證授權(quán)
認(rèn)證授權(quán)主要是對所有action的訪問第一入口認(rèn)證,對用戶的訪問做第一道監(jiān)管過濾攔截閘口。
實現(xiàn)方式:需要自定義一個類,繼承AuthorizeAttribute并重寫OnAuthorization,在OnAuthorization中能夠獲取到用戶請求的所有Request信息,其實我們做的所有認(rèn)證攔截操作,其所有數(shù)據(jù)支撐都是來自Request中。
具體驗證流程設(shè)計:
IP白名單:這個主要針對的是API做IP限制,只有指定IP才可訪問,非指定IP直接返回
請求頻率控制:這個主要是控制用戶的訪問頻率,主要是針對API做,超出請求頻率直接返回。
登錄認(rèn)證:登錄認(rèn)證一般我們采用的是通過在請求的header中傳遞token的方式來進(jìn)行驗證,這樣即使用與一般的MVC登錄認(rèn)證,也使用與API接口的Auth認(rèn)證,并且也不依賴于用戶前端js設(shè)置等。
授權(quán)認(rèn)證:授權(quán)認(rèn)證就簡單了,主要是驗證該用戶是否具有該權(quán)限,如果不具有,直接做下相應(yīng)的返回處理。
MVC和API異同:
命名空間:MVC:System.Web.Http.Filters;API:System.Web.Mvc
注入方式:在注入方式上,主要包括:全局->控制器Controller->行為Action
全局注冊:針對所有系統(tǒng)的所有Aciton都使用
Controller:只針對該Controller下的Action起作用
Action:只針對該Action起作用
其中全局注冊,針對MVC和API還有一些差異:
MVC在 FilterConfig.cs中注入
filters.Add(new XYHMVCAuthorizeAttribute());
API 在 WebApiConfig.cs 中注入
config.Filters.Add(new XYHAPIAuthorizeAttribute());
注意事項:在實際使用中,針對認(rèn)證授權(quán),我們一般都是添加全局認(rèn)證,但是,有的action又不需要做認(rèn)證,比如本來的登錄Action等等,那么該如何排除呢?其實也很簡單,我們只需要在自定定義一個Attribute集成Attribute,或者系統(tǒng)的AllowAnonymousAttribute,在不需要驗證的action中只需要注冊上對于的Attribute,并在驗證前做一個過濾即可,比如:
// 有 AllowAnonymous 屬性的接口直接開綠燈
if (actionContext.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any())
{
return;
}
API AuthFilterAttribute實例代碼
/// <summary>
/// 授權(quán)認(rèn)證過濾器
/// </summary>
public class XYHAPIAuthFilterAttribute : AuthorizationFilterAttribute
{
/// <summary>
/// 認(rèn)證授權(quán)驗證
/// </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)
{
// 簽名校驗
// 獲取全部的請求參數(shù)
Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();
result = new AuthCheckService().SignCheck(queryParameters, apikey);
if (result.Code == MResultCodeEnum.successCode)
{
// 如果有NoChekokenFilterAttribute 標(biāo)簽 那么直接不做token認(rèn)證
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,是否最終還是會執(zhí)行action中的代碼
actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
//需要自己指定輸出內(nèi)容和類型
HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
HttpContext.Current.Response.End(); // 此處結(jié)束響應(yīng),就不會走路由系統(tǒng)
}
}
}
MVC AuthFilterAttribute實例代碼
/// <summary>
/// MVC自定義授權(quán)
/// 認(rèn)證授權(quán)有兩個重寫方法
/// 具體的認(rèn)證邏輯實現(xiàn):AuthorizeCore 這個里面寫具體的認(rèn)證邏輯,認(rèn)證成功返回true,反之返回false
/// 認(rèn)證失敗處理邏輯:HandleUnauthorizedRequest 前一步返回 false時,就會執(zhí)行到該方法中
/// 但是,我平時在應(yīng)用過程中,一般都是在AuthorizeCore根據(jù)不同的認(rèn)證結(jié)果,直接做認(rèn)證后的邏輯處理
/// </summary>
public class XYHMVCAuthorizeAttribute : AuthorizeAttribute
{
/// <summary>
/// 認(rèn)證邏輯
/// </summary>
/// <param name="filterContext">過濾器上下文</param>
public override void OnAuthorization(AuthorizationContext filterContext)
{
// 此處主要寫認(rèn)證授權(quán)的相關(guān)驗證邏輯
// 該部分的驗證一般包括兩個部分
// 登錄權(quán)限校驗
// --我們的一般處理方式是,通過header中傳遞一個token來進(jìn)行邏輯驗證
// --當(dāng)然不同的系統(tǒng)在設(shè)計上也不盡相同,有的也會采用session等方式來驗證
// --所以最終還是根據(jù)其項目本身的實際情況來進(jìn)行對應(yīng)的邏輯操作
// 具體的頁面權(quán)限校驗
// --該部分的驗證是具體的到頁面權(quán)限驗證
// --我看有得小伙伴沒有做到這一個程度,直接將這一步放在前端js來驗證,這樣不是很安全,但是可以攔住小白用戶
// --當(dāng)然有的系統(tǒng)根本就沒有做權(quán)限控制,那就更不需要這一個邏輯了。
// --所以最終還是根據(jù)其項目本身的實際情況來進(jìn)行對應(yīng)的邏輯操作
// 現(xiàn)在用一個粗暴的方式來簡單模擬實現(xiàn)過,用系統(tǒng)當(dāng)前時間段秒廚藝3,取余數(shù)
// 當(dāng)余數(shù)為0:認(rèn)證授權(quán)通過
// 1:代表為登錄,調(diào)整至登錄頁面
// 2:代表無訪問權(quán)限,調(diào)整至無權(quán)限提示頁面
// 當(dāng)然,在這也還可以做一些IP白名單,IP黑名單驗證 請求頻率驗證等等
// 說到這而,還有一點需要注意,如果我們選擇的是全局注冊該過濾器,那么如果有的頁面根本不需要權(quán)限認(rèn)證,比如登錄頁面,那么我們可以給不需要權(quán)限的認(rèn)證的控制器或者action添加一個特殊的注解 AllowAnonymous ,來排除
// 獲取Request的幾個關(guān)鍵信息
HttpRequest httpRequest = HttpContext.Current.Request;
string acitonName = filterContext.ActionDescriptor.ActionName;
string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
// 注意:如果認(rèn)證不通過,需要設(shè)置filterContext.Result的值,否則還是會執(zhí)行action中的邏輯
filterContext.Result = null;
int thisSecond = System.DateTime.Now.Second;
switch (thisSecond % 3)
{
case 0:
// 認(rèn)證授權(quán)通過
break;
case 1:
// 代表為登錄,調(diào)整至登錄頁面
// 只有設(shè)置了Result才會終結(jié)操作
filterContext.Result = new RedirectResult("/html/Login.html");
break;
case 2:
// 代表無訪問權(quán)限,調(diào)整至無權(quán)限提示頁面
filterContext.Result = new RedirectResult("/html/NoAuth.html");
break;
}
}
}
ActionFilter:自定義過濾器
自定義過濾器,主要是監(jiān)控action請求前后,處理結(jié)果返回前后的事件。其中API只有請求前后的兩個方法。
|
重新方法 |
方法功能描述 |
使用于 |
|
OnActionExecuting |
一個請求在進(jìn)入到aciton邏輯前執(zhí)行 |
MVC、API |
|
OnActionExecuted |
一個請求aciton邏輯執(zhí)行后執(zhí)行 |
MVC、API |
|
OnResultExecuting |
對應(yīng)的view視圖渲染前執(zhí)行 |
MVC |
|
OnResultExecuted |
對應(yīng)的view視圖渲染后執(zhí)行 |
MVC |
在這幾個方法中,我們一般主要用來記錄交互日志,記錄每一個步驟的耗時情況,以便后續(xù)系統(tǒng)優(yōu)化使用。具體的使用,根據(jù)自身的業(yè)務(wù)場景使用。
其中MVC和API的異同點,和上面說的認(rèn)證授權(quán)的異同類似,不在詳細(xì)說明。
下面的一個實例代碼:
API定義過濾器實例DEMO代碼
/// <summary>
/// Action過濾器
/// </summary>
public class XYHAPICustomActionFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Action執(zhí)行開始
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
}
/// <summary>
/// action執(zhí)行以后
/// </summary>
/// <param name="actionContext"></param>
public override void OnActionExecuted(HttpActionExecutedContext actionContext)
{
try
{
// 構(gòu)建一個日志數(shù)據(jù)模型
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();
// 由于數(shù)據(jù)庫中最大只能存儲4000字符串,所以對返回值做一個截取
if (!string.IsNullOrEmpty(apiRequestLogsM.RESPONSE_INFOR) &&
apiRequestLogsM.RESPONSE_INFOR.Length > 4000)
{
apiRequestLogsM.RESPONSE_INFOR = apiRequestLogsM.RESPONSE_INFOR.Substring(0, 2000);
}
// 請求參數(shù)
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,并自定義實現(xiàn):OnException方法即可。
在OnException我們可以根據(jù)自身需要,做一些相應(yīng)的邏輯處理,比如記錄異常日志,便于后續(xù)問題分析跟進(jìn)。
OnException還有一個很重要的處理,那就是對異常結(jié)果的統(tǒng)一包裝,返回一個很友好的結(jié)果給用戶,避免把一些不必要的信息返回給用戶。比如:針對MVC,那么跟進(jìn)不同異常,統(tǒng)一調(diào)整至友好的提示頁面等等;針對API,那么我們可以一個統(tǒng)一的返回幾個封裝,便于用戶統(tǒng)一處理結(jié)果。
MVC 的異常處理實例代碼:
/// <summary>
/// MVC自定義異常處理機制
/// 說道異常處理,其實我們腦海中的第一反應(yīng),也該是try/cache操作
/// 但是在實際開發(fā)中,很有可能地址錯誤根本就進(jìn)入不到try中,又或者沒有被try處理到異常
/// 該類就發(fā)揮了作用,能夠很好的未經(jīng)捕獲的異常,并做相應(yīng)的邏輯處理
/// 自定義異常機制,主要集成HandleErrorAttribute 重寫其OnException方法
/// </summary>
public class XYHMVCHandleError : HandleErrorAttribute
{
/// <summary>
/// 處理異常
/// </summary>
/// <param name="filterContext">異常上下文</param>
public override void OnException(ExceptionContext filterContext)
{
// 我們在平時的項目中,異常處理一般有兩個作用
// 1:記錄異常的詳細(xì)日志,便于事后分析日志
// 2:對異常的統(tǒng)一友好處理,比如根據(jù)異常類型重定向到友好提示頁面
// 在這里面既能獲取到未經(jīng)處理的異常信息,也能獲取到請求信息
// 在此可以根據(jù)實際項目需要做相應(yīng)的邏輯處理
// 下面簡單的列舉了幾個關(guān)鍵信息獲取方式
// 控制器名稱 注意,這樣獲取出來的是一個文件的全路徑
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();
// 獲取全部的請求參數(shù)
HttpRequest httpRequest = HttpContext.Current.Request;
Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();
// 獲取異常對象
Exception ex = filterContext.Exception;
// 異常描述信息
string exMessage = ex.Message;
// 異常堆棧信息
string stackTrace = ex.StackTrace;
// 根據(jù)實際情況記錄日志(文本日志、數(shù)據(jù)庫日志,建議具體步驟采用異步方式來完成)
filterContext.ExceptionHandled = true;
// 模擬根據(jù)不同的做對應(yīng)的邏輯處理
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自定義異常處理機制
/// 說道異常處理,其實我們腦海中的第一反應(yīng),也該是try/cache操作
/// 但是在實際開發(fā)中,很有可能地址錯誤根本就進(jìn)入不到try中,又或者沒有被try處理到異常
/// 該類就發(fā)揮了作用,能夠很好的未經(jīng)捕獲的異常,并做相應(yīng)的邏輯處理
/// 自定義異常機制,主要集成ExceptionFilterAttribute 重寫其OnException方法
/// </summary>
public class XYHAPIHandleError : ExceptionFilterAttribute
{
/// <summary>
/// 處理異常
/// </summary>
/// <param name="actionExecutedContext">異常上下文</param>
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
// 我們在平時的項目中,異常處理一般有兩個作用
// 1:記錄異常的詳細(xì)日志,便于事后分析日志
// 2:對異常的統(tǒng)一友好處理,比如根據(jù)異常類型重定向到友好提示頁面
// 在這里面既能獲取到未經(jīng)處理的異常信息,也能獲取到請求信息
// 在此可以根據(jù)實際項目需要做相應(yīng)的邏輯處理
// 下面簡單的列舉了幾個關(guān)鍵信息獲取方式
// 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();
// 獲取全部的請求參數(shù)
HttpRequest httpRequest = HttpContext.Current.Request;
Dictionary<string, string> queryParameters = httpRequest.GetAllQueryParameters();
// 獲取異常對象
Exception ex = actionExecutedContext.Exception;
// 異常描述信息
string exMessage = ex.Message;
// 異常堆棧信息
string stackTrace = ex.StackTrace;
// 根據(jù)實際情況記錄日志(文本日志、數(shù)據(jù)庫日志,建議具體步驟采用異步方式來完成)
// 自己的記錄日志落地邏輯略 ......
// 構(gòu)建統(tǒng)一的內(nèi)部異常處理機制,相當(dāng)于對異常做一層統(tǒng)一包裝暴露
MBaseResult<string> result = new MBaseResult<string>()
{
Code = MResultCodeEnum.systemErrorCode,
Message = MResultCodeEnum.systemError
};
actionExecutedContext.Response = new HttpResponseMessage(HttpStatusCode.OK);
//需要自己指定輸出內(nèi)容和類型
HttpContext.Current.Response.ContentType = "text/html;charset=utf-8";
HttpContext.Current.Response.Write(JsonConvert.SerializeObject(result));
HttpContext.Current.Response.End(); // 此處結(jié)束響應(yīng),就不會走路由系統(tǒng)
}
}
總結(jié)
.net過濾器,我個人的一句話理解就是:對action的各個階段進(jìn)行統(tǒng)一的監(jiān)控處理等操作。.net過濾器中,其中每一個種過濾器的執(zhí)行先后順序為:Authorize(授權(quán))-->ActionFilter(自定義)-->HandleError(錯誤處理)
好了,就先聊到這而,如果什么地方說的不對之處,多多指點和多多包涵。我自己寫了一個練習(xí)DEMO,里面會有每一種情況的處理說明。有興趣的可以取下載下來看一看,謝謝。
DEMO在GitHub地址為:https://github.com/xuyuanhong0902/XYH.FilterTest.git
到此這篇關(guān)于聊一聊Asp.net過濾器Filter那一些事的文章就介紹到這了,更多相關(guān)Asp.net過濾器Filter內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決 The Controls collection cannot be modified because the co
在.aspx或.ascx的如果包括%,并在.aspx, .ascs中使用了AjaxToolkit中的控件,那么很可能會引發(fā)這個問題,下面給出具體的解決方法。2010-10-10
ASP.NET.4.5.1+MVC5.0設(shè)置系統(tǒng)角色與權(quán)限(一)
這篇文章主要介紹了ASP.NET.4.5.1+MVC5.0設(shè)置系統(tǒng)角色與權(quán)限的部分內(nèi)容,后續(xù)我們將繼續(xù)討論這個話題,希望小伙伴們喜歡。2015-01-01
介紹幾個ASP.NET中容易忽略但卻很重要的方法函數(shù)
介紹幾個ASP.NET中容易忽略但卻很重要的方法函數(shù)...2006-09-09
asp.net實現(xiàn)服務(wù)器文件下載到本地的方法
這篇文章主要介紹了asp.net實現(xiàn)服務(wù)器文件下載到本地的方法,需要的朋友可以參考下2017-02-02
C# 文件保存到數(shù)據(jù)庫中或者從數(shù)據(jù)庫中讀取文件
在編程中我們常常會遇到“將文件保存到數(shù)據(jù)庫中”這樣一個問題,雖然這已不是什么高難度的問題,但對于一些剛剛開始編程的朋友來說可能是有一點困難。2009-03-03

