MVC5 AntiForgeryToken - 如何处理“提供的防伪令牌是为用户“”,但当前用户是“xxx”的异常?
MVC5 AntiForgeryToken - 如何处理“提供的防伪令牌是为用户“”,但当前用户是“xxx”的异常?
我想通过AntiforgeryToken属性来保护我们的登录操作 - 我知道为什么会发生上述主题中的异常,但是我似乎找不到任何好的解决方案。\n假设我们有以下情况:\n
- \n
- 现在是早上8点,应用程序的用户开始工作,他们坐下并开始登录过程 - 现在很有可能有些用户会获得相同的ValidationToken。在第一个用户登录后,所有其他用户在尝试登录时都会看到上述异常(或其他自定义异常屏幕)。
- 某个用户登录后,不小心按下了“返回”按钮,然后尝试再次登录 - 虽然这种情况不太可能发生,但是它确实可能发生,我不希望用户在这种情况下看到异常。
\n
\n
\n所以问题很简单 - 如何防止上述情况发生,或者如何处理它们以使用户不会注意到任何问题。我尝试了以下方法:\n
- \n
- 在Global.asax的Application_Start中设置AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; - 这样做没有解决问题,我仍然会收到相同的异常。
- 在带有[ValidateAntiForgeryToken]属性的方法上设置[OutputCache(NoStore = true, Duration = 0, VaryByParam = \"None\")] - 同样没有运气。
\n
\n
\n现在我在动作体中考虑手动验证令牌,捕获错误,并检查尝试是否由匿名用户进行:\n
public ActionResult SomeAction() { try { AntiForgery.Validate(); } catch(HttpAntiForgeryException ex) { if(String.IsNullOrEmpty(HttpContext.User.Identity.Name)) { throw; } } //其他动作体在这里 //.. //.. }
\n上述方法似乎可以防止错误发生 - 但是它是否安全? 还有其他选择吗?\n提前致谢。\n最好的问候。\n编辑:\n最终的“解决方案”是在登录表单上禁用令牌验证 - 可能有更好的处理方法,但似乎我找到的所有解决方案都是类似于我上面提出的不雅解决方法。\n由于无法知道这些替代方法有多“安全”(如果它们根本安全),我们决定在登录时禁用令牌验证。
MVC5 AntiForgeryToken - 如何处理“提供的防伪令牌是给用户“”,但当前用户是“xxx”的异常?
问题原因:
- 当在应用程序的其他部分使用令牌验证时,过滤器会覆盖所有关于AntiforgeryToken的异常。
解决方法:
- 在全局过滤器中添加一个自定义过滤器HandleAntiforgeryTokenErrorAttribute来处理防伪令牌异常。
- 在HandleAntiforgeryTokenErrorAttribute的OnException方法中,判断当前请求的控制器和动作名称。
- 如果控制器为"Account"且动作为"Login",则处理异常并重定向到登录页面。
- 如果不是上述情况,则继续使用基类的OnException方法处理异常。
代码示例:
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new HandleAntiforgeryTokenErrorAttribute() { ExceptionType = typeof(HttpAntiForgeryException) }); } } public class HandleAntiforgeryTokenErrorAttribute : HandleErrorAttribute { public override void OnException(ExceptionContext filterContext) { string actionName = filterContext.Controller.ControllerContext.RouteData.Values["action"].ToString(); string controllerName = filterContext.Controller.ControllerContext.RouteData.Values["controller"].ToString(); if (actionName.ToLower() == "login" && controllerName.ToLower() == "account") { // 处理错误 // 在此处处理错误,可以记录日志、添加通知等... // 处理异常 filterContext.ExceptionHandled = true; filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary(new { action = "Login", controller = "Account" })); } else { base.OnException(filterContext); } } }
注意事项:
- 上述代码将特殊处理在"Account/Login"方法中抛出的异常,而其他使用[ValidateAntiForgeryToken]的方法则继续使用基类的处理方式。
问题的出现原因是因为在使用MVC5的AntiforgeryToken时,出现了提供的防伪标记是给用户"",但当前用户是"xxx"的异常。解决方法可以尝试以下几种:
1. 设置AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
,将名称标识符添加到防伪标记中。
2. 使用一个脚本来记录原始提交的日期和时间,以防止使用相同标记进行第二次提交。可以使用以下jQuery插件来实现:
jQuery.fn.preventDoubleSubmission = function() { $(this).on('submit',function(e){ var $form = $(this); if ($form.data('submitted') === true) { e.preventDefault(); } else { $form.data('submitted', true); } }); return this; };
3. 尽量减少用户使用“返回”按钮和双击的冲动,可以通过以下几种方式来限制用户的这种行为:
- 确保表单错误提示信息清晰明确,让用户知道出了什么问题。
- 在表单提交之间保持表单状态,除了密码或信用卡号码之类的敏感信息,使用MVC表单助手可以很轻松地实现。
- 如果表单分布在选项卡或隐藏的div中,可以显示出错误实际来源的控制器布局或表单,而不仅仅是将用户重定向到主页或第一个选项卡。
4. 当防伪标记验证失败时,网站会抛出System.Web.Mvc.HttpAntiForgeryException类型的异常。可以通过捕获HttpAntiForgeryException来提供更具信息性的错误页面,例如:
private void Application_Error(object sender, EventArgs e) { Exception ex = Server.GetLastError(); if (ex is HttpAntiForgeryException) { Response.Clear(); Server.ClearError(); Response.Redirect("/error/antiforgery", true); } }
5. 另一种方法是记录错误并将用户返回到登录界面。可以创建一个HandleAntiforgeryTokenErrorAttribute类来重写OnException方法,并在Global Filter中注册该类。
这个问题的解决方法有很多种,可以根据具体情况选择适合自己的方法。不过需要注意的是,在登录表单上使用防伪标记可能没有太大的意义,因为用户在登录之前是匿名用户,所以没有必要伪造用户。