.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证
在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; }
全局异常处理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时再详细介绍。