• .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;
            }
    View Code

    全局异常处理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时再详细介绍。

  • 相关阅读:
    WPF 策略模式
    老陈 WPF
    老陈 ASP.NET封装
    小石头 封装
    典型用户故事
    整数的四则运算
    对git的认识
    如何学习计算机
    团队编程二——web应用之人事管理系统
    团队编程——web应用之人事管理系统
  • 原文地址:https://www.cnblogs.com/taotaozhuanyong/p/11575590.html
Copyright © 2020-2023  润新知