【问题标题】:MVC5 AntiForgeryToken - how to handle "The provided anti-forgery token was meant for user "", but the current user is "xxx"." exception?MVC5 AntiForgeryToken - 如何处理“提供的防伪令牌用于用户”,但当前用户是“xxx”。例外?
【发布时间】:2015-12-15 15:55:58
【问题描述】:

我想通过 AntiforgeryToken 属性保护我们的登录操作 - 我知道为什么会出现该主题的异常,但我似乎找不到任何好的解决方案。

假设我们有以下情况:

  1. 现在是上午 8:00,应用程序用户开始工作,他们坐下来开始登录过程 - 现在很可能某些用户会得到相同的结果验证令牌。在第一个登录后 - 所有其他人在尝试登录时都会看到上述异常(或其他一些自定义异常屏幕)。

  2. 某些用户登录,然后不小心按下了“返回”按钮并尝试再次登录 - 虽然这种情况不太可能发生,但它可能会发生,我不希望用户查看异常情况。

所以问题很简单 - 如何防止上述情况,或者如何处理它们以便用户不会注意到任何事情。我尝试了以下方法:

  1. Application_Start in Global.asax 中设置 AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; - 它没有解决问题,我仍然得到同样的例外
  2. 在具有 [ValidateAntiForgeryToken] 属性的方法上设置 [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")] - 再次,没有运气

现在我正在考虑手动验证动作正文中的令牌,捕获错误,并检查匿名用户是否进行了尝试:

public ActionResult SomeAction()
{
    try
    {
        AntiForgery.Validate();
    }
    catch(HttpAntiForgeryException ex)
    {
        if(String.IsNullOrEmpty(HttpContext.User.Identity.Name))
        {
            throw;
        }
    }

    //Rest of action body here
    //..
    //..
}

以上似乎可以防止错误 - 但它安全吗? 有哪些替代方法?

提前致谢。

最好的问候。

编辑:

最终的“解决方案”是禁用登录表单上的令牌验证 - 可能有更好的方法来处理它,但我发现的所有解决方案似乎都是类似于我上面提出的丑陋的解决方法。

由于无法知道这些替代方案有多“安全”(如果它们完全安全的话),我们决定在登录时禁用令牌验证。

【问题讨论】:

  • 可以使用脚本禁用双重提交,stackoverflow.com/questions/2830542/…
  • 嗨,当用户点击浏览器上的返回按钮时,我遇到了同样的问题。你的结局是什么?
  • @VAAA:请参阅我的问题的编辑部分 - 因为只有登录表单给了我们这个问题,所以最后我们只是在那里禁用了令牌验证。我们认为这根本不值得麻烦,坦率地说,我找不到任何“干净”的解决方案——只有一些丑陋的解决方法(比如我用 try-catch 发布的那个)。

标签: exception-handling asp.net-mvc-5 antiforgerytoken


【解决方案1】:

尝试设置(在 global.cs 中):

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;

这会将名称标识符添加到您的令牌中,

对于双重登录问题,请尝试使用脚本记录原始提交的日期和时间,以停止使用相同令牌的第二次提交。

// jQuery plugin to prevent double submission of forms
jQuery.fn.preventDoubleSubmission = function() {
  $(this).on('submit',function(e){
    var $form = $(this);

    if ($form.data('submitted') === true) {
      // Previously submitted - don't submit again
      e.preventDefault();
    } else {
      // Mark it so that the next submit can be ignored
      $form.data('submitted', true);
    }
  });

  // Keep chainability
  return this;
};

所以我们知道一件事;用户喜欢后退键,有双击的习惯,这是AntiforgeryToken的一个大问题。

但根据您的应用程序的用途,有一些方法可以限制他们这样做的强迫性。最简单的方法是尽最大努力让访问者觉得他们不需要“回退”他们的更改请求。

确保表单错误消息清晰简洁,以确保 用户知道出了什么问题。语境错误会加分。

在表单提交之间始终保持表单状态。除了 密码或信用卡号码,没有任何借口感谢 MVC 表单助手。 @Html.LabelFor(x => x.FirstName)

如果表单分布在选项卡或隐藏的 div 中,例如 像 Angular 或 ember.js 这样的 SPA 框架,很聪明,可以展示 错误实际源自的控制器布局或表单 显示错误时的表单提交。不要只是指挥他们 到家庭控制器或第一个选项卡。

“发生了什么事?” - 让用户了解情况

当 AntiForgeryToken 未验证您的网站时,将抛出 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(); //make sure you log the exception first
        Response.Redirect("/error/antiforgery", true);
    }
}

您的/error/antiforgery 视图可以告诉他们抱歉,您已尝试两次提交相同的信息

另一个想法是记录错误并将用户返回到登录屏幕:

创建一个覆盖 OnException 方法的HandleAntiforgeryTokenErrorAttribute 类。

HandleAntiforgeryTokenErrorAttribute.cs:

public class HandleAntiforgeryTokenErrorAttribute : HandleErrorAttribute
{
    public override void OnException(ExceptionContext filterContext)
    {
        filterContext.ExceptionHandled = true;
        filterContext.Result = new RedirectToRouteResult(
            new RouteValueDictionary(new { action = "Login", controller = "Account" }));
    }
}

全局过滤器:

public class FilterConfig
{
    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new HandleErrorAttribute());
        filters.Add(new HandleAntiforgeryTokenErrorAttribute()
            { ExceptionType = typeof(HttpAntiForgeryException) }
        );
    }
}

我还会使用一些工具来记录您的所有信息,因为登录是您应用程序的关键部分

NLog 用于有关关键应用程序异常(包括 Web 异常)的常规日志记录和电子邮件。

Elmah 用于过滤网络异常并通过电子邮件发送。

编辑: 此外,您可能想查看一个名为 SafeForm 的 jQuery 插件。 Link

编辑:

我看过很多关于这个的争论,每个人对这个问题的看法都有道理,我怎么看(取自owasp.org

跨站请求伪造 (CSRF) 是一种强制最终用户的攻击 在它们所在的 Web 应用程序上执行不需要的操作 目前已通过身份验证,CSRF 攻击专门针对状态更改请求,而不是窃取数据。防伪令牌是 特定于“谁登录”。所以一旦你登录,然后返回, 旧令牌不再有效

现在,如果用户 IP 地址发生变化,我还使用授权的 IP 地址登录,以便为我的应用程序分配 2 因素授权,因此如果跨站点请求伪造正在发挥作用,则用户不会匹配 IP 地址并请求 2因素授权。几乎就像安全路由器的工作方式一样。但是,如果您想将其保留在您的登录页面上,只要您设置了友好的错误页面,我就不会发现问题,人们不会感到沮丧,因为他们会看到他们做错了什么。

【讨论】:

  • 感谢您的回答,并为迟到的回复感到抱歉-我稍微修改了您的解决方案(我使用隐藏的输入字段来传递:“已提交”值),现在“返回按钮”的情况似乎是已解决,但是 AD1 的情况仍然存在,这可以通过在浏览器的两个不同选项卡中加载登录表单来模拟,我可以清楚地看到两个选项卡的“___RequestverificationToken”cookie 是相同的,当我提交时我得到相同和以前一样的错误。虽然我可以捕捉到这个异常,但我仍然没有办法处理它......
  • 说实话 这个问题的真正答案就是你不应该在登录表单上使用防伪令牌!在登录表单上“伪造”用户是没有意义的——他们还没有登录,在登录之前都是匿名用户!所以它使用匿名用户创建Requestverification Token,用户登录后应该在表单上使用防伪令牌,因为同一浏览器和计算机上的选项卡之间没有什么不同。
  • 说实话 - 我现在有点困惑,因为关于这个主题似乎有两种不同的观点,例如这里:security.stackexchange.com/questions/2120/… 我找到了相当广泛的示例答案,其中声明应在登录表单上使用防伪令牌...另一方面,还有许多其他帖子指出其他情况...我将等待更多其他可能的想法-但如果没有任何结果,我想我会做按照您的建议并在登录时禁用令牌验证:)
  • 您是否在登录控制器中尝试过“[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]”。这将自动发出新的页面加载请求。
  • 再次抱歉很晚才回复。我试过'[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]',但它无助于防止任何情况(可能是因为浏览器在用户单击“返回”时仍然使用自己的缓存)。无论如何,感谢所有建议 - 现在,我们将在登录表单上禁用防伪令牌 - 这似乎更麻烦,然后值得。
【解决方案2】:

我只是想添加到pool pro's answer - 关于过滤器部分 - 必须小心,因为这将覆盖所有异常关于AntiforgeryToken,如果您(和我一样)在应用程序的其他部分有此令牌验证,您可能会考虑向过滤器添加一些验证:

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")
        {
            //Handle Error
            //In here you handle the error, either by logging, adding notifications, etc...

            //Handle Exception
            filterContext.ExceptionHandled = true;
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary(new { action = "Login", controller = "Account" }));
        }
        else
        {
            base.OnException(filterContext);
        }
    }
}

请注意,我将Account/Login 方法引发此异常的情况与可能使用[ValidateAntiForgeryToken] 的所有其他方法分开。

【讨论】:

    猜你喜欢
    • 2016-03-19
    • 2015-02-01
    • 2013-12-18
    • 2013-04-30
    • 2013-02-04
    • 2013-10-06
    • 2015-01-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多