zoukankan      html  css  js  c++  java
  • Implementing MVC 5 IAuthenticationFilter

    Implementing MVC 5 IAuthenticationFilter

    问题

    I don't understand the purpose/difference of OnAuthentication and OnAuthenticationChallenge aside from OnAuthentication running before an action executes and OnAuthenticationChallenge runs after an action executes but before the action result is processed.

    It seems as though either one of them (OnAuthentication or OnAuthenticationChallenge) can do all that is needed for authentication. Why the need for 2 methods?

    My understanding is OnAuthentication is where we put the logic of authenticating (or should this logic be in actual action method?), connecting to data store and checking for the user account. OnAuthenticationChallenge is where we redirect to login page if not authenticated. Is this correct? Why can't I just redirect on OnAuthentication and not implement OnAuthenticationChallenge. I know there is something I am missing; could someone explain it to me?

    Also what is the best practice to store an authenticated user so that succeeding requests wouldn't have to connect to db to check again for user?

    Please bear in mind that I am new to ASP.NET MVC.

    回答

    Those methods are really intended for different purposes:

    • IAuthenticationFilter.OnAuthentication should be used for setting the principal, the principal being the object identifying the user.

    You can also set a result in this method like an HttpUnauthorisedResult (which would save you from executing an additional authorization filter). While this is possible, I like the separation of concerns between the different filters.

    • IAuthenticationFilter.OnAuthenticationChallenge is used to add a "challenge" to the result before it is returned to the user.

    • This is always executed right before the result is returned to the user, which means it might be executed at different points of the pipeline on different requests. See the explanation of ControllerActionInvoker.InvokeAction below.

    • Using this method for "authorization" purposes (like checking if a user is logged in or in a certain role) might be a bad idea since it might get executed AFTER the controller action code, so you might have changed something in the db before this gets executed!

    • The idea is that this method can be used to contribute to the result, rather than perform critical authorization checks. For example you could use it to convert an HttpUnauthorisedResult into a redirect to different login pages based on some logic. Or you could hold some user changes, redirect him to another page where you can request additional confirmation/information and depending on the answer finally commit or discard those changes.

    • IAuthorizationFilter.OnAuthorization should still be used to perform authentication checks, like checking if the user is logged in or belongs to a certain role.

    You can get a better idea if you check the source code for ControllerActionInvoker.InvokeAction. The following will happen when executing an action:

    1. IAuthenticationFilter.OnAuthentication is called for every authentication filter. If the principal is updated in the AuthenticationContext, then both context.HttpContext.User and Thread.CurrentPrincipal are updated.

    2. If any authentication filter set a result, for example setting a 404 result, then OnAuthenticationChallenge is called for every authentication filter, which would allow changing the result before being returned. (You could for example convert it into a redirect to login). After the challenges, the result is returned without proceeding to step 3.

    3. If none of the authentication filters set a result, then for every IAuthorizationFilter its OnAuthorization method is executed.

    4. As in step 2, if any authorization filter set a result, for example setting a 404 result, then OnAuthenticationChallenge is called for every authentication filter. After the challenges, the result is returned without proceeding to step 3.

    5. If none of the authorization filters set a result, then it will proceed to executing the action (Taking into account request validation and any action filter)

    6. After action is executed and before the result is returned, OnAuthenticationChallenge is called for every authentication filter

    I have copied the current code of ControllerActionInvoker.InvokeAction here as a reference, but you can use the link above to see the latest version:

    https://github.com/aspnet/AspNetWebStack/blob/master/src/System.Web.Mvc/ControllerActionInvoker.cs#L231

    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;
    }

    As for not hitting the db on every request when setting the principal, you could use some sort of server side caching.

  • 相关阅读:
    Objective-C代码规范
    Http中Get/Post请求区别
    使用Vitamio打造自己的Android万能播放器
    Vitamio
    图片瀑布流
    TCP与UDP
    SQLite基本操作总结
    IOS文件操作的两种方式:NSFileManager操作和流操作
    JSON和XML
    一些iOS常用的第三方库和控件及第三方框架还有动画
  • 原文地址:https://www.cnblogs.com/chucklu/p/13362854.html
Copyright © 2011-2022 走看看