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:
-
IAuthenticationFilter.OnAuthentication
is called for every authentication filter. If the principal is updated in the AuthenticationContext, then bothcontext.HttpContext.User
andThread.CurrentPrincipal
are updated. -
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. -
If none of the authentication filters set a result, then for every
IAuthorizationFilter
itsOnAuthorization
method is executed. -
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. -
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)
-
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:
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.
作者:Chuck Lu GitHub |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2019-07-22 Walkthrough: My first WPF desktop application
2019-07-22 cropper.js
2019-07-22 ShareX的使用
2016-07-22 Download file using libcurl in C/C++
2016-07-22 EnumWindows function
2015-07-22 修改Hosts