聊一聊Asp.net过滤器Filter那一些事
最近在整理优化.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")[]; 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 % )
{
case :
// 认证授权通过
break;
case :
// 代表为登录,调整至登录页面
// 只有设置了Result才会终结操作
filterContext.Result = new RedirectResult("/html/Login.html");
break;
case :
// 代表无访问权限,调整至无权限提示页面
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")[];
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 > )
{
apiRequestLogsM.RESPONSE_INFOR = apiRequestLogsM.RESPONSE_INFOR.Substring(, );
} // 请求参数
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>= && statusCode<)
{
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
Controller
聊一聊Asp.net过滤器Filter那一些事的更多相关文章
- ASP.NET没有魔法——ASP.NET MVC 过滤器(Filter)
上一篇文章介绍了使用Authorize特性实现了ASP.NET MVC中针对Controller或者Action的授权功能,实际上这个特性是MVC功能的一部分,被称为过滤器(Filter),它是一种面 ...
- Asp.Net Core Filter 深入浅出的那些事-AOP
一.前言 在分享ASP.NET Core Filter 使用之前,先来谈谈AOP,什么是AOP 呢? AOP全称Aspect Oriented Programming意为面向切面编程,也叫做面向方法编 ...
- MVC之 自定义过滤器(Filter)
MVC之 自定义过滤器(Filter) 一.自定义Filter 自定义Filter需要继承ActionFilterAttribute抽象类,重写其中需要的方法,来看下ActionFilterAttri ...
- ASP.NET过滤器的应用
在J2EE Web开发中有过滤器filter,该filter可以对指定的URL访问进行拦截,并执行过滤器的方法,根据实际应用情况,在过滤器中修改请求的代码.判断会话信息,也可以做权限控制,总之这个过滤 ...
- AngularJS过滤器filter入门
在开发中,经常会遇到这样的场景 如用户的性别分为“男”和“女”,在数据库中保存的值为1和0,用户在查看自己的性别时后端返回的值自然是1或0,前端要转换为“男”或“女”再显示出来: 如我要换个羽毛球拍, ...
- ASP.Net MVC Filter验证用户登录
一.Filter是什么 ASP.NetMVC模式自带的过滤器Filter,是一种声明式编程方式,支持四种过滤器类型,各自是:Authorization(授权),Action(行为),Result(结果 ...
- 利用过滤器Filter和特性Attribute实现对Web API返回结果的封装和统一异常处理
在我们开发Web API应用的时候,我们可以借鉴ABP框架的过滤器Filter和特性Attribute的应用,实现对Web API返回结果的封装和统一异常处理,本篇随笔介绍利用AuthorizeAtt ...
- ASP.NET Core Filter与IOC的羁绊
前言 我们在使用ASP.NET Core进行服务端应用开发的时候,或多或少都会涉及到使用Filter的场景.Filter简单来说是Action的拦截器,它可以在Action执行之前或者之后对请求信息进 ...
- Angularjs在控制器(controller.js)的js代码中使用过滤器($filter)格式化日期/时间实例
Angularjs内置的过滤器(filter)为我们的数据信息格式化提供了比较强大的功能,比如:格式化时间,日期.格式化数字精度.语言本地化.格式化货币等等.但这些过滤器一般都是在VIEW中使用的,比 ...
随机推荐
- 复变函数-MINDMAPS-continuous updating
- react-debug
最近练习react的时候遇到一些问题: 在redux模式下,同一个api依据参数获取不同data的时候,返回的data相同 原因:多次调用该接口时,action的type相同,导致对应于该接口的每个r ...
- 【DevCloud · 敏捷智库】如何拆分用户故事
提起用户故事拆分,我们听得最多的就是INVEST原则(关于INVEST原则可以参考文章“用户故事等于需求说明”——你一定没有写好用户故事),但很多人面临的问题是拿到一个较大的用户故事时,该如何拆分才能 ...
- mysql基本操作汇总
1.数据库操作 (1)创建数据库 CREATE DATABASE <数据库名>; 例子: CREATE DATABASE IF NOT EXISTS RUNOOB DEFAULT CHAR ...
- 矩阵重叠面积计算 线段树hdu1542
Atlantis Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Su ...
- CF894B Ralph And His Magic Field
题目链接:http://codeforces.com/contest/894/problem/B 题目大意: 往一个 \(n \times m\) 的网格中填数字 \((1 \le n,m \le 1 ...
- JVM调优总结(六)-新一代的垃圾回收算法
垃圾回收的瓶颈 传统分代垃圾回收方式,已经在一定程度上把垃圾回收给应用带来的负担降到了最小,把应用的吞吐量推到了一个极限.但是他无法解决的一个问题,就是Full GC所带来的应用暂停.在一些对实时性要 ...
- Android_适配器(adapter)之SimpleAdapter
概述 SimpleAdapter是一种 简单的适配器,将静态数据映射到布局xml对应的视图上.它也是BaseAdapter的子类. SimpleAdapter数据映射的组件有3类(从官网api或Sim ...
- [Wireshark]_002_玩转数据包
通过前一篇文章,我们大概了解了Wireshark,现在可以准备好进行数据包的捕获和分析了.这一片我们将讲到如何使用捕获文件,分析数据包以及时间格式显示等. 1.使用捕获文件 进行数据包分析时,其实很大 ...
- java类的方法的使用
类的方法:提供某种功能的实现: 实例:public void eat (){ } public String getName(){ } public void setName(String n){ ...