zoukankan      html  css  js  c++  java
  • 对InvokeAction简略分析了解验证失败为什么Action还会继续执行

    一、前言

    有些同学使用AuthorizationFilter来进行用户是否登录验证,如果未登录就跳到登录页。

    很简单的一个场景,但是有些同学会发现虽然验证失败了,但是整个Action还会执行一遍。

    于是google啊google啊,然后找到了解决方案,但是不知为啥,下面就对InvokeAction的简略分析,说说到底是为啥。

    二、分析InvokeAction执行顺序

    1、首先获取Controller和Action描述类

    ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
    ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

    2、大家都知道每个Action执行前会先执行一些Filters,所以首先需要获取所有FilterScope为Action的Filters

    FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

    3、Filter的执行是有顺序的,AuthenticationFilter最先执行,接下来就是执行AuthenticationFilter的代码

    AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);

    这里要注意,AuthenticationContext有个属性Result,如果验证失败一定要设置这个属性的值,否则看看下面的代码就知道了。

    if (authenticationContext.Result != null)
    {
        // An authentication filter signaled that we should short-circuit the request. Let all
        // authentication filters contribute to an action result (to combine authentication
        // challenges). Then, run this action result.
        AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
            controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
            authenticationContext.Result);
        InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
    }
    else
    {
        ...
    }

    看到了吧,如果你设置Result的值,此时就会执行你设置的Result,如果不设置就会继续走下去,会走哪去,一直往后看,你就知道了。

    插一下,我们先看看 InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)是什么东东。

    protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult)
    {
        actionResult.ExecuteResult(controllerContext);
    }

    这个方法看字面意思是执行一个ActionResult,还是看两个简单的例子吧。

    (1)ContentResult

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
    
        HttpResponseBase response = context.HttpContext.Response;
    
        if (!String.IsNullOrEmpty(ContentType))
        {
            response.ContentType = ContentType;
        }
        if (ContentEncoding != null)
        {
            response.ContentEncoding = ContentEncoding;
        }
        if (Content != null)
        {
            response.Write(Content);
        }
    }

     很简单吧,就是把你设置的Content输出到页面中,用的也是response.Write(content)方法,所以在Mvc中完全可以用ContentResult替换response.Write(content).

     (2)HttpStatusCodeResult

    public override void ExecuteResult(ControllerContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }
    
        context.HttpContext.Response.StatusCode = StatusCode;
        if (StatusDescription != null)
        {
            context.HttpContext.Response.StatusDescription = StatusDescription;
        }
    }

    HttpStatusCodeResult 是HttpUnauthorizedResult 的基类,HttpUnauthorizedResult是什么?自己看字面意思也看出来了。

    这个Result的ExecuteResult的更简单,只是设置Response.StatusCode和StatusDescription。

    4、回到InvokeAction中来,接着InvokeAuthenticationFilters这个方法的执行往下看 

    if (authenticationContext.Result != null)已经解释了,然后看看else。else中就开始执行AuthorizationFilters了,此处终于知道为什么AuthenticationFilters执行在前了吧。

    只有authenticationContext.Result不设置值的时候,才会执行AuthorizationFilters,所以如果你想AuthenticationFilters验证失败之后,就退出Action,一定要设置Result.

    其实AuthorizationFilters的执行方式和AuthenticationFilters类似,如果设置了AuthorizationContext的Result属性,就会执行Result,否则就继续往下执行。

    AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
    if (authorizationContext.Result != null)
    {
        // An authorization filter signaled that we should short-circuit the request. Let all
        // authentication filters contribute to an action result (to combine authentication
        // challenges). Then, run this action result.
        AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
            controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
            authorizationContext.Result);
        InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
    }
    else
    {
        // 如果你没有其他的Filters、没有设置OnActionXXX等等,你的Action内的代码就会在这里被调用执行了。
        ...
    }

    看到这里,是不是有些同学会想:“原来如此啊”。

    三、总结

    由于微软没有提供AuthenticationFilter对应的基类,只提供了一个IAuthenticationFilter,所以我们就用AuthorizeAttribute来进行总结。

    一些同学自定义AuthorizationFilters的时候,代码结尾没有调用基类的 OnAuthorization,也没有设置FilterContext的Result属性,只是进行Response.Redirect或者其他认为跳出Action执行的代码。这样其实等于没有进行验证,Action中的代码还是会执行。

    在进行AuthorizationFilters的时候,如果想验证失败之后,不执行Action的方法,可以有以下两种方法:

    (1)调用基类的 OnAuthorization,我们看看基类 OnAuthorization 都干了啥?

    public virtual void OnAuthorization(AuthorizationContext filterContext)
    {
        ...
    
        if (AuthorizeCore(filterContext.HttpContext))
        {
            ...
        }
        else
        {
            HandleUnauthorizedRequest(filterContext); // 主要看这个方法
        }
    }
    protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        // Returns HTTP 401 - see comment in HttpUnauthorizedResult.cs.
        filterContext.Result = new HttpUnauthorizedResult();
    }

    HttpUnauthorizedResult:这个类有没有看着眼熟,对了,就是前面讲Result中的第二个例子的子类。

    (2)给filterContext的Result属性设置一个Result,即使是EmptyResult都可以,如果你想输出点内容到页面中,那就使用ContetResult,前面说过它是通过调用Response.Write()来实现的。

    最后附上ControllerActionInvoker:InvokeAction(L230)方法的详细内容:

    public virtual bool InvokeAction(ControllerContext controllerContext, string actionName)
    {
        if (controllerContext == null)
        {
            throw new ArgumentNullException("controllerContext");
        }
    
        Contract.Assert(controllerContext.RouteData != null);
        if (String.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch())
        {
            throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
        }
    
        ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
        ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
    
        if (actionDescriptor != null)
        {
            FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);
    
            try
            {
                AuthenticationContext authenticationContext = InvokeAuthenticationFilters(controllerContext, filterInfo.AuthenticationFilters, actionDescriptor);
    
                if (authenticationContext.Result != null)
                {
                    // An authentication filter signaled that we should short-circuit the request. Let all
                    // authentication filters contribute to an action result (to combine authentication
                    // challenges). Then, run this action result.
                    AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                        controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                        authenticationContext.Result);
                    InvokeActionResult(controllerContext, challengeContext.Result ?? authenticationContext.Result);
                }
                else
                {
                    AuthorizationContext authorizationContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
                    if (authorizationContext.Result != null)
                    {
                        // An authorization filter signaled that we should short-circuit the request. Let all
                        // authentication filters contribute to an action result (to combine authentication
                        // challenges). Then, run this action result.
                        AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                            controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                            authorizationContext.Result);
                        InvokeActionResult(controllerContext, challengeContext.Result ?? authorizationContext.Result);
                    }
                    else
                    {
                        if (controllerContext.Controller.ValidateRequest)
                        {
                            ValidateRequest(controllerContext);
                        }
    
                        IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
                        ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
    
                        // The action succeeded. Let all authentication filters contribute to an action result (to
                        // combine authentication challenges; some authentication filters need to do negotiation
                        // even on a successful result). Then, run this action result.
                        AuthenticationChallengeContext challengeContext = InvokeAuthenticationFiltersChallenge(
                            controllerContext, filterInfo.AuthenticationFilters, actionDescriptor,
                            postActionContext.Result);
                        InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters,
                            challengeContext.Result ?? postActionContext.Result);
                    }
                }
            }
            catch (ThreadAbortException)
            {
                // This type of exception occurs as a result of Response.Redirect(), but we special-case so that
                // the filters don't see this as an error.
                throw;
            }
            catch (Exception ex)
            {
                // something blew up, so execute the exception filters
                ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
                if (!exceptionContext.ExceptionHandled)
                {
                    throw;
                }
                InvokeActionResult(controllerContext, exceptionContext.Result);
            }
    
            return true;
        }
    
        // notify controller that no method matched
        return false;
    }
    View Code
  • 相关阅读:
    Systemd 指令
    2018年书单
    2017年书单
    Centos7 Devstack [Rocky] 重启后无法联网
    kvm虚拟机操作相关命令及虚拟机和镜像密码修改
    负载均衡原理-转
    用配置文件里面的参数值替换yaml模板中的变量值【python】
    linux工具之sar
    利用系统缓存优化程序的运行效率
    Elasticsearch单机部署
  • 原文地址:https://www.cnblogs.com/acles/p/5293414.html
Copyright © 2011-2022 走看看