“返回”按钮和防伪标记

25

我遇到了一个与防伪属性相关的运行时错误

请按以下步骤进行操作:

  1. 创建一个MVC Web应用程序并启动它
  2. 注册 joe@acme.org
  3. 退出登录
  4. 注册jane@acme.org
  5. 退出登录
  6. joe@acme.org的身份登录
  7. 点击返回按钮
  8. jane@acme.org的身份登录

错误:提供的防伪令牌是针对与当前用户不同的基于声明的用户。

如何避免发生这种错误?

4个回答

35

我刚遇到了同样的问题,通过禁用登录视图的缓存来解决。这实际上很有道理,而且不需要编写代码或异常处理。

我的登录控制器方法现在看起来像这样:

[AllowAnonymous]
[OutputCache(NoStore = true, Location = OutputCacheLocation.None)]
public ActionResult LogOn(Uri returnUrl)

当缓存被禁用时,如果用户在浏览器上单击后退按钮,则会向服务器发出新请求并再次传递页面,并且防伪令牌将设置为正确的用户。

我认为这是解决问题的一种更加清洁、简单和合乎逻辑的方法。


当我登录、注销并再次登录(所有这些都使用同一用户)时,此代码对我无效。在第二次登录时返回错误:“提供的防伪令牌是针对与当前用户不同的基于声明的用户”。 - mggSoft
对我来说很有效,好主意。 - DennisWelu
我发现如果你清除了POST请求的缓存并使用后退按钮导航到它,Chrome会显示一个_ERR_CACHE_MISS_错误消息。 - ajbeaven
我曾经遇到过这个问题,有两个原因:(1)用户双击“登录”按钮;(2)登录页面被错误地缓存,就像这里所描述的那样。你必须告诉客户端浏览器不要缓存登录页面,我认为这是一个好的安全实践。 - philw
对我来说很有效。顺带一提,这也是 Firefox 中已知但存在的错误的解决方法,当您使用 Javascript 禁用按钮时,它会在页面刷新和后退后仍保持禁用状态。请搜索“Firefox 按钮保持禁用”。 - nmit026
显示剩余2条评论

25

这是一种忽略错误并将用户返回到登录屏幕的方法。这只是一个例子。

创建一个名为HandleAntiforgeryTokenErrorAttribute的新类,该类继承自HandleErrorAttribute。重写OnException方法。

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

前往您的FilterConfig类并将该属性注册为全局过滤器。

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

那应该放在哪里?错误甚至在调用 public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) 之前就发生了。 - WhiskerBiscuit
@RowanFreeman,给出的解决方案不起作用。在点击“登录ActionResult”之前,它就会出现错误。 “提供的防伪标记是为用户“”,但当前用户是“joe@acme.org”。”。 - RajeshKdev
3
谢谢更新。因此,上述代码已经很好地处理了错误。但是我想知道正确的方法是什么?因为用户已经登录了。但它正在将我重定向到登录页面。从那里,我可以在没有登录的情况下导航到任何页面。希望你理解我的功能问题。 - RajeshKdev
@RowanFreeman 不错!如果你在 HandleAntiforgeryTokenErrorAttribute 类中设置 ExceptionType = typeof(HttpAntiForgeryException),那会更好,因为这是它的目的。 - Enrique Carro
如果有人真的试图提供伪造的令牌,那么错误会被忽略吗? - JMG
显示剩余3条评论

4

已接受的答案仅捕获所有异常,因为它不像原始HandleErrorAttribute一样按异常类型过滤它们。

使用以下代码仅处理HttpAntiForgeryException:

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

public class HandleAntiforgeryTokenErrorAttribute : HandleErrorAttribute
{
    public HandleAntiforgeryTokenErrorAttribute()
    {
        ExceptionType = typeof(HttpAntiForgeryException);
    }

    public override void OnException(ExceptionContext filterContext)
    {
        if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
        {
            return;
        }

        filterContext.ExceptionHandled = true;
        filterContext.Result =
            new RedirectToRouteResult(
                new RouteValueDictionary(
                    new {
                            area = string.Empty,
                            action = "Index",
                            controller = "Home"
                        }));

    }
}

1

缓存的旧页面会在使用“返回”按钮时重新启用,其中包含旧的防伪标记,这会导致异常。Rowan Freeman 的全局过滤器解决方案会将用户重定向到登录页面。 然而,这个缓存问题还会导致站点提供一个带有旧令牌的旧登录页面。 提交表单将导致相同的异常。 因此,在我看来,应该实施 Rowan Freeman 和 julealgon 的两种解决方案。

理论上,对于每个页面避免缓存也可以解决问题,但代价显著(延迟,带宽)。我选择重新路由到登录界面以便能够使用缓存并避免在登录界面上进行缓存以减轻异常,即同时实现两者。


这应该是一条注释,但您的声望不足。请解释为什么@julealgon的解决方案本身不足以解决问题。它似乎可以解决问题。 - iCollect.it Ltd
1
julealgon的解决方案只在浏览器遵循禁用缓存的指令时才能起作用(在某些情况下,Internet Explorer可能会忽略此设置并仍然缓存页面)。在这种情况下,Rowan的解决方案是一个不错的备选方案。 - hobwell

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