Подтвердить что ты не робот

Внедрение MVC 5 IAuthenticationFilter

Я не понимаю цели/отличия OnAuthentication и OnAuthenticationChallenge в стороне от функции OnAuthentication, выполняемой до того, как действие выполняется, а OnAuthenticationChallenge запускается после выполнения действия, но до того, как результат действия будет обработан.

Кажется, что любой из них (OnAuthentication или OnAuthenticationChallenge) может делать все, что необходимо для аутентификации. Зачем нужна 2 метода?

Мое понимание - это OnAuthentication, где мы помещаем логику аутентификации (или должна ли эта логика быть в методе фактических действий?), подключаясь к хранилищу данных и проверяя учетную запись пользователя. OnAuthenticationChallenge - это место, где мы перенаправляем страницу входа, если она не аутентифицирована. Это верно? Почему я не могу просто перенаправить на OnAuthentication и не реализовать OnAuthenticationChallenge. Я знаю, что мне что-то не хватает; может кто-нибудь объяснить это мне?

Также лучше всего хранить аутентифицированный пользователь, чтобы последующие запросы не приходилось подключаться к db, чтобы снова проверить пользователя?

Пожалуйста, имейте в виду, что я новичок в ASP.NET MVC.

4b9b3361

Ответ 1

Эти методы действительно предназначены для разных целей:

  • IAuthenticationFilter.OnAuthentication следует использовать для установки принципала, главным из которых является объект, идентифицирующий пользователя.

    Вы также можете установить результат в этом методе как HttpUnauthorisedResult (что избавит вас от выполнения дополнительного фильтра авторизации). Хотя это возможно, мне нравится разделение проблем между различными фильтрами.

  • IAuthenticationFilter.OnAuthenticationChallenge используется для добавления "задачи" к результату до того, как он будет возвращен пользователю.

    • Это всегда выполняется непосредственно перед возвратом результата пользователю, что означает, что он может выполняться в разных точках конвейера на разных запросах. См. Объяснение ControllerActionInvoker.InvokeAction ниже.

    • Использование этого метода для целей "авторизации" (например, проверка того, что пользователь входит в систему или в определенной роли) может быть плохой идеей, так как он может выполняться ПОСЛЕ кода действия контроллера, так что вы, возможно, изменили что-то в db, прежде чем это будет выполнено!

    • Идея состоит в том, что этот метод может использоваться для внесения вклада в результат, а не для выполнения критических проверок полномочий. Например, вы можете использовать его для преобразования HttpUnauthorisedResult в перенаправление на разные страницы входа на основе некоторой логики. Или вы можете провести некоторые изменения пользователя, перенаправить его на другую страницу, где вы можете запросить дополнительное подтверждение/информацию, и в зависимости от ответа, наконец, совершите или отмените эти изменения.

  • IAuthorizationFilter.OnAuthorization все равно должна использоваться для выполнения проверок проверки подлинности, например, проверка того, входит ли пользователь в систему или принадлежит определенной роли.

Вы можете получить лучшую идею, если вы проверите исходный код ControllerActionInvoker.InvokeAction. При выполнении действия произойдет следующее:

  • IAuthenticationFilter.OnAuthentication вызывается для каждого фильтра проверки подлинности. Если директор обновляется в AuthenticationContext, то обновляются как context.HttpContext.User, так и Thread.CurrentPrincipal.

  • Если какой-либо фильтр проверки подлинности установил результат, например, установив результат 404, тогда для каждого фильтра проверки подлинности вызывается OnAuthenticationChallenge, что позволит изменить результат перед возвратом. (Вы можете, например, преобразовать его в перенаправление на логин). После вызовов результат возвращается, не переходя к шагу 3.

  • Если ни один из фильтров проверки подлинности не устанавливает результат, то для каждого IAuthorizationFilter выполняется его метод OnAuthorization.

  • Как и на шаге 2, если какой-либо фильтр авторизации задал результат, например, установив результат 404, тогда для каждого фильтра проверки подлинности вызывается OnAuthenticationChallenge. После вызовов результат возвращается, не переходя к шагу 3.

  • Если ни один из фильтров авторизации не установил результат, он продолжит выполнение действия (принимая во внимание проверку запроса и любой фильтр действий)

  • После выполнения действия и до того, как результат будет возвращен, для каждого фильтра проверки подлинности вызывается OnAuthenticationChallenge

Я скопировал текущий код ControllerActionInvoker.InvokeAction здесь как ссылку, но вы можете использовать ссылку выше, чтобы увидеть последнюю версию:

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

Как для того, чтобы не ударить db по каждому запросу при настройке принципала, вы можете использовать какое-то кеширование на стороне сервера.