如何在防伪令牌错误后继续登录用户?扩展HandleErrorAttribute问题
解决已登录用户再次登录时的防伪令牌错误并自动完成新登录
我明白你遇到的困扰了——当已登录用户不小心再次提交登录POST请求时,防伪令牌验证失败抛出错误,你已经在HandleErrorAttribute里处理了登出,但请求却没能进入登录Action,只给用户留下了空白页。这是因为你只是标记了异常已处理,但没有让请求继续执行;而且就算强制推进,旧的防伪令牌和刚登出的匿名用户依然不匹配,还是会触发错误。
下面是具体的解决方案:
核心问题分析
默认的ValidateAntiForgeryTokenAttribute会在Action执行前验证令牌,当已登录用户提交登录请求时,令牌绑定的是旧用户的Claims,所以验证失败抛出异常。你的HandleErrorAttribute捕获异常后登出了用户,但此时请求的生命周期已经中断,不会再进入Login Action;而且就算强制进入,旧令牌依然无效,还是会触发错误。
解决方案:自定义防伪令牌验证属性
我们需要在验证阶段就处理登出逻辑,而不是等到异常抛出后再补救。这样可以让请求顺利进入Login Action,完成新用户的登录。
步骤1:创建自定义的ValidateAntiForgeryTokenAttribute
using System.Web; using System.Web.Helpers; using System.Web.Mvc; using Microsoft.Owin.Security; public class LoginAwareAntiForgeryTokenAttribute : ValidateAntiForgeryTokenAttribute { protected override void ValidateCore(HttpContextBase httpContext) { // 检查是否是已登录用户提交的登录POST请求 if (httpContext.User.Identity.IsAuthenticated && httpContext.Request.CurrentExecutionFilePath.Equals("/Account/Login", StringComparison.OrdinalIgnoreCase) && httpContext.Request.HttpMethod == HttpVerbs.Post.ToString()) { // 1. 登出当前已登录用户 var authenticationManager = httpContext.GetOwinContext().Authentication; authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); // 2. 清除Session和旧的防伪令牌Cookie httpContext.Session.Abandon(); var antiForgeryCookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName]; if (antiForgeryCookie != null) { httpContext.Response.Cookies.Add(new HttpCookie(AntiForgeryConfig.CookieName) { Expires = DateTime.Now.AddDays(-1), Path = antiForgeryCookie.Path, HttpOnly = antiForgeryCookie.HttpOnly, Secure = antiForgeryCookie.Secure }); } // 3. 跳过本次防伪验证(因为旧令牌已失效,且我们需要让请求进入Login Action处理新登录) return; } // 其他情况走默认验证逻辑 base.ValidateCore(httpContext); } }
步骤2:替换Login POST方法上的验证属性
把原来的[ValidateAntiForgeryToken]换成我们自定义的[LoginAwareAntiForgeryToken],同时可以移除[AntiForgeryHandleErrorAttribute](因为现在不会抛出异常了):
[HttpPost] [AllowAnonymous] [LoginAwareAntiForgeryToken] public ActionResult Login(LoginViewModel model, string returnUrl) { // 你的登录逻辑保持不变 if (ModelState.IsValid) { // 验证用户凭证,登录新用户... // 示例代码: var user = UserManager.Find(model.UserName, model.Password); if (user != null) { AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie); var identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie); AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = model.RememberMe }, identity); return RedirectToLocal(returnUrl); } else { ModelState.AddModelError("", "Invalid username or password."); } } // 如果验证失败,返回登录页面 return View(model); }
为什么这个方案有效?
- 提前处理登出:在防伪验证之前就完成旧用户的登出和旧令牌的清除,从根源避免了异常的抛出。
- 允许请求进入Action:跳过了本次的防伪验证(因为旧令牌已经失效,而我们需要处理新的登录请求),让请求顺利进入Login Action。
- 无缝完成新登录:Login Action会正常处理新的用户凭证,完成登录流程,用户不会看到空白页或错误信息。
额外注意事项
- 如果你需要严格的防伪验证,可以考虑在登出后引导用户刷新页面重新提交,但这不符合你自动完成登录的需求,所以跳过验证是更合适的选择。
- 确保你的
UserManager和AuthenticationManager的注入或获取逻辑正确,避免空引用错误。
内容的提问来源于stack exchange,提问作者Mark Seymour
相关产品推荐
相关产品推荐

