欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

程序员文章站 2022-05-29 10:32:12
在webform中,验证的流程大致如下图: 在AOP中: 在Filter中: AuthorizeAttribute权限验证 登录后有权限控制,有的页面是需要用户登录才能访问的,需要在访问页面增加一个验证,也不能每个action都一遍。 1、写一个CustomAuthorAttribute,继承自Au ......

在webform中,验证的流程大致如下图:

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 在aop中:

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 在filter中:

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

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;//已经被我处理了
         }
     }
 }

这个是要重新跳转的地址:

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 一定要考虑到是不是ajax请求的

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 .NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 多种异常情况,能不能进入自定义的异常呢?

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

好像一个俄罗斯套娃,或者说洋葱模型

 

 .NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

在同一个位置注册的生效顺序,同一个位置按照先后顺序生效,还有一个order的参数,不设置order默认是1,设置之后按照从小到大执行

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 

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 = "超出频率" }
         };
     }
 }

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 .NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

.NET MVC5简介(四)Filter和AuthorizeAttribute权限验证

 

 

 

 filter这么厉害,有没有什么局限性????

虽然很丰富,但是只能以action为单位,action内部调用别的类库,加操作就做不到!这种就得靠ioc+aop扩展。

本篇只是介绍了.net framework mvc 中的过滤器filter(权限特性、action、result、exception),其实在.net core mvc 增加了resourcefilter,加了这个特性,资源特性,action/result /exception三个特性没有什么变化。后面记录到到.net core mvc时再详细介绍。