"PostAuthenticateRequest" 什么时候执行?

16

这是我的Global.asax.cs文件:

public class MvcApplication : System.Web.HttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        ...
    }

    protected void Application_Start()
    {
        this.PostAuthenticateRequest += new EventHandler(MvcApplication_PostAuthenticateRequest);
    }

    // This method never called by requests...
    protected void MvcApplication_PostAuthenticateRequest(object sender, EventArgs e)
    {
        HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie != null)
        {
            FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
            var identity = new GenericIdentity(authTicket.Name, "Forms");
            var principal = new GenericPrincipal(identity, new string[] { });
            Context.User = principal;
        }
    }
}

PostAuthenticateRequest 什么时候被执行?

2个回答

24
根据文档所述:

当安全模块建立了用户的身份时发生。

...

在AuthenticateRequest事件发生后,会引发PostAuthenticateRequest事件。订阅PostAuthenticateRequest事件的功能可以访问由PostAuthenticateRequest处理的任何数据。

另外,这里有ASP.NET页面生命周期

但由于您的问题标记为ASP.NET MVC,我强烈建议您将其转换为自定义的[Authorize]属性,而不是使用此事件。例如:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var isAuthorized = base.AuthorizeCore(httpContext);
        if (isAuthorized)
        {
            var authCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie != null)
            {
                var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                var identity = new GenericIdentity(authTicket.Name, "Forms");
                var principal = new GenericPrincipal(identity, new string[] { });
                httpContext.User = principal;
            }
        }
        return isAuthorized;
    }
}

现在使用[MyAuthorize]属性为您的控制器/操作添加装饰:

[MyAuthorize]
public ActionResult Foo()
{
    // if you got here the User property will be the custom
    // principal you injected in the authorize attribute
    ...
}

5
为什么你推荐在所有控制器上应用过滤器,当在事件处理程序的一个地方进行更改似乎更加清晰?这样做有什么好处? - Andrew Savinykh
1
对于这个例子,用var identity = new FormsIdentity(authTicket);替换var identity = new GenericIdentity(authTicket.Name, "Forms");可能更合适。 - Scott Coates
6
在每个页面中,PostAuthenticateRequest事件可能会被调用多次。使用自定义授权属性可确保您的代码每个请求仅被调用一次。 - Mark
我尝试了PostAuthenticate和Windows身份验证方法的组合,但遇到了问题http://stackoverflow.com/questions/14439497/mixing-windows-authentication-with-sql-server-custom-authentication-mvc3。现在我打算采纳您的建议并实施。我希望这不会引起任何问题。 - Murali Murugesan
@Darin Dimitrov,我是否也应该像您为HttpContext.User所做的那样设置Thread.CurrentPrincipal呢?(即System.Threading.Thread.CurrentPrincipal = principal;) - SherleyDev

10

如果您将代码放在PostAuthenticateRequest上,每个资源(如页面引用的图像和样式表)都会作为单独的请求触发此事件,因此您可能会受到多次请求的影响。

如果您采用@Darin的答案,AuthorizeAttribute在返回false时不会呈现操作,但人们可能仍需要呈现它,即使它是公共页面(无限制访问),您可能希望在authTicket的userData部分上显示保存的“Display Name”。

为此,我建议在ActionFilterAttribute(AuthenticationFilter)中加载authCookie:

public class LoadCustomAuthTicket : ActionFilterAttribute, IAuthenticationFilter
{
    public void OnAuthentication(AuthenticationContext filterContext)
    {
        if (!filterContext.Principal.Identity.IsAuthenticated)
            return;

        HttpCookie authCookie = filterContext.HttpContext.Request.Cookies[FormsAuthentication.FormsCookieName];

        if (authCookie == null)
            return;

        FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
        var identity = new GenericIdentity(authTicket.Name, "Forms");
        var principal = new GenericPrincipal(identity, new string[] { });

        // Make sure the Principal's are in sync. see: https://www.hanselman.com/blog/SystemThreadingThreadCurrentPrincipalVsSystemWebHttpContextCurrentUserOrWhyFormsAuthenticationCanBeSubtle.aspx
        filterContext.Principal = filterContext.HttpContext.User = System.Threading.Thread.CurrentPrincipal = principal;

    }
    public void OnAuthenticationChallenge(AuthenticationChallengeContext filterContext)
    {
        //This method is responsible for validating the current principal and permitting the execution of the current action/request.
        //Here you should validate if the current principle is valid / permitted to invoke the current action. (However I would place this logic to an authorization filter)
        //filterContext.Result = new RedirectToRouteResult("CustomErrorPage",null);
    }
}

并且在 global.asax.cs 文件中。

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new LoadCustomAuthTicket());
    }

这样你也不需要在所有的动作中都填充属性。


如果在一个操作上同时应用了自定义授权属性和该操作过滤器步骤,会不会发生冲突? - Andrew Hoffman
另外,有没有办法确保此属性在授权属性之前执行?理想情况下,我们只需要在一个位置加载cookie,然后再执行任何属性。 - Andrew Hoffman
发现了一些相关且有趣的操作顺序信息。https://dev59.com/fWw15IYBdhLWcg3whME1 - Andrew Hoffman
我刚刚更新了代码,将AuthenticationFilter放在ActionFilter之前,因为在升级到MVC5 .net 4.5后,之前的ActionFilter代码无法正常工作。 - Joao Leme

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接