【问题标题】:How do I solve an AntiForgeryToken exception that occurs after an iisreset in my ASP.Net MVC app?如何解决我的 ASP.Net MVC 应用程序中的 iisreset 后发生的 AntiForgeryToken 异常?
【发布时间】:2011-01-13 11:27:22
【问题描述】:

我在使用 ASP.Net MVC 中的 AntiForgeryToken 时遇到问题。如果我在我的网络服务器上执行 iisreset 并且用户继续他们的会话,他们会被退回到登录页面。不可怕,但是 AntiForgery 令牌爆炸了,唯一的办法就是把浏览器上的 cookie 炸掉。

对于版本 1 的测试版,它在为我读回 cookie 时经常出错,所以我过去常常在请求验证令牌之前对其进行清理,但在它发布时已修复。

现在我想我会回滚到修复测试版问题的代码,但我不禁认为我错过了一些东西。有没有更简单的解决方案,我应该放弃他们的助手并从头开始创建一个新的吗?我觉得很多问题在于它与旧的 ASP.Net 管道如此紧密地联系在一起,并试图将其混杂在一起做一些它实际上并没有被设计用来做的事情。

我查看了 ASP.Net MVC 2 RC 的源代码,看起来代码没有太大变化,所以虽然我没有尝试过,但我认为那里没有任何答案.

这是异常堆栈跟踪的相关部分。

编辑:我刚刚意识到我没有提到这只是试图在 GET 请求中插入令牌。这不是在您启动 POST 时发生的验证。

System.Web.Mvc.HttpAntiForgeryException: A required anti-forgery token was not
supplied or was invalid.
---> System.Web.HttpException: Validation of viewstate MAC failed. If this 
application is hosted by a Web Farm or cluster, ensure that <machineKey> 
configuration specifies the same validationKey and validation algorithm. 
AutoGenerate cannot be used in a cluster.
---> System.Web.UI.ViewStateException: Invalid viewstate. 
  Client IP: 127.0.0.1
  Port: 4991
  User-Agent: scrubbed
  ViewState: scrubbed
  Referer: blah
  Path: /oursite/Account/Login
---> System.Security.Cryptography.CryptographicException: Padding is invalid and
cannot be removed.
at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast)
at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount)
at System.Security.Cryptography.CryptoStream.FlushFinalBlock()
at System.Web.Configuration.MachineKeySection.EncryptOrDecryptData(Boolean fEncrypt, Byte[] buf, Byte[] modifier, Int32 start, Int32 length, IVType ivType, Boolean useValidationSymAlgo)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
--- End of inner exception stack trace ---
--- End of inner exception stack trace ---
at System.Web.UI.ViewStateException.ThrowError(Exception inner, String persistedState, String errorPageMessage, Boolean macValidationError)
at System.Web.UI.ViewStateException.ThrowMacValidationError(Exception inner, String persistedState)
at System.Web.UI.ObjectStateFormatter.Deserialize(String inputString)
at System.Web.UI.ObjectStateFormatter.System.Web.UI.IStateFormatter.Deserialize(String serializedState)
at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken)
--- End of inner exception stack trace ---
at System.Web.Mvc.AntiForgeryDataSerializer.Deserialize(String serializedToken)
at System.Web.Mvc.HtmlHelper.GetAntiForgeryTokenAndSetCookie(String salt, String domain, String path)
at System.Web.Mvc.HtmlHelper.AntiForgeryToken(String salt, String domain, String path)

【问题讨论】:

标签: .net asp.net asp.net-mvc csrf


【解决方案1】:

我遇到了这个问题,要解决您需要做的就是在您的网络配置中添加一个显式机器密钥...

<machineKey validationKey="D82960E6B6E9B9029D4CAB2F597B5B4AF631E3C182670855D25FBDE1BFAFE19EFDE92ABBD1020FC1B2AE455D5B5F8D094325597CE1A7F8B15173407199C85A16" decryptionKey="577404C3A13F154908D7A5649EEC8D7C8A92C35A25A3EC078B426BB09D426A71" validation="SHA1" decryption="AES" /> 

确保将其放置在 web.config 内...

<system.web>

【讨论】:

【解决方案2】:

您可以将AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; 添加到global.asax

protected void Application_Start() {
    AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
}

【讨论】:

  • 这到底是做什么的?
  • @TimothyGroote from asp.net/mvc/overview/security/…:“一个布尔值,指示反 XSRF 系统是否应该停用其对基于声明的身份的支持。如果此值为 true,则系统将假定 IIdentity.Name适合用作每个用户的唯一标识符,并且不会尝试将 IClaimsIdentity 或 ClClaimsIdentity 设置为 WIF / ACS / 基于声明的身份验证部分中所述的特殊情况。默认值为 false。"这要求 IIdentity.Name 是一个足够唯一的标识符以确保安全。
【解决方案3】:

如果您的MachineKey 设置为自动生成,那么您的验证令牌等将无法在应用程序重新启动时继续存在 - ASP.NET 将在启动时生成一个新密钥,然后无法解密标记正确。

如果你经常看到这个,我建议:

  1. 配置静态 MachineKey(您应该能够在应用程序级别执行此操作),请参阅 "How to: Configure a MachineKey" 了解更多信息
  2. 在使用站点时尽量不要执行 IIS 重置1

1 最好的方法是使用负载平衡的应用程序,这需要您设置静态 MachineKey。另一种选择是通过在站点根目录下放置一个名为 app_offline.htm 的文件来关闭站点,这将使站点脱机并显示您的消息 - 至少用户会预料到事情会出错。

【讨论】:

  • 啊,这很可能是问题所在。我会试一试,假设它有效,我会将此标记为答案。不做 iisreset 是个好主意,但我只是想确保它在现实世界中尽可能可靠地工作。
  • 这里有一些关于这个主题的非常好的文章adam.kahtava.com/journal/2009/11/23/…adam.kahtava.com/journal/2009/11/25/…
  • 我有正确的机器密钥,但有时我有 HttpAntiForgeryException。 :(我不知道为什么。:/
【解决方案4】:

实际上我发现这在我的登录操作中起作用:

    public ActionResult LogOn()
    {
        formsAuthentication.SignOut();

        Response.Cookies.Clear();

        Session[SessionKeys.USER_SESSION_KEY] = null;
        Session.Clear();
        Session.Abandon();


        return View();
    }

重要的部分是:Response.Cookies.Clear();

【讨论】:

  • 那一半有效。 Zaph 关于机器密钥的观点效果更好,因为它可以防止您在 iisreset 后跳到登录页面。它还可以防止 cookie 由于 iisreset 而变得无效。
  • 我也有一个静态机器密钥,但遇到了同样的问题。
  • 在已经登录的情况下尝试登录不应受到注销用户的惩罚。如果用户在不知情的情况下打开了多个窗口并意外地在过时的登录页面上操作,则用户可能会在其他窗口中丢失工作/状态。这里更好的解决方案是将它们转发到登录屏幕,例如Zia forwards them to an antiforgery page
【解决方案5】:

现在我已经采用了一个解决方案,如果抛出异常,它会清除 cookie。如果再次抛出异常,我将让它原样发生。

我暂时不会将此标记为“答案”,希望有人有更好的答案。

public static class MyAntiForgeryExtensions
{
    // Methods
    public static string MyAntiForgeryToken(this HtmlHelper helper)
    {
        return MyAntiForgeryToken(helper, null);
    }

    public static string MyAntiForgeryToken(this HtmlHelper helper, string salt)
    {
        string fragment;
        string path = helper.ViewContext.HttpContext.Request.ApplicationPath;
        try
        {
            fragment = helper.AntiForgeryToken(salt, null, path);
        }
        catch (HttpAntiForgeryException)
        {
            // okay, scrub the cookie and have another go.
            string cookieName = GetAntiForgeryTokenName(path);
            helper.ViewContext.HttpContext.Request.Cookies.Remove(cookieName);
            fragment = helper.AntiForgeryToken(salt, null, path);
        }
        return fragment;
    }

    #region AntiForgeryData code that shouldn't be sealed
    // Copied from AntiForgeryData since they aren't accessible.
    internal static string GetAntiForgeryTokenName(string appPath) {
        if (String.IsNullOrEmpty(appPath)) {
            return "__RequestVerificationToken";
        }
        else {
            return "__RequestVerificationToken_" + Base64EncodeForCookieName(appPath);
        }
    }
    private static string Base64EncodeForCookieName(string s) {
        byte[] rawBytes = Encoding.UTF8.GetBytes(s);
        string base64String = Convert.ToBase64String(rawBytes);

        // replace base64-specific characters with characters that are safe for a cookie name
        return base64String.Replace('+', '.').Replace('/', '-').Replace('=', '_');
    }
    #endregion
}

【讨论】:

  • 我也做了同样的事情。我认为这是 AntiForgeryToken 方法的一个很大的设计缺陷,就像您的 cmets 建议的那样,MVC 代码是内部/密封的更令人沮丧,因为它使我们无法轻松修复它!
  • 在相关说明中,ValidateAntiForgeryToken 属性被密封也令人沮丧,因为它也有一些缺点。实际上,我最终抓住了 VAFT 源,将其解封,并创建了我自己的子类,该子类可以应用于整个 Controller 并支持 JSON 请求。
  • 我要为 MVC3 支持更改的一件事是您的静态帮助程序扩展方法应该返回 MvcHtmlString,因为 helper.AntiForgeryToken(string, string, string) 返回该类型而不是纯字符串。
【解决方案6】:

如果我在我的网络服务器上执行 iisreset 并且用户继续他们的 会话他们被退回到登录 页面。

iisreset 没有理由将用户带到登录页面。如果您使用 cookie 跟踪身份验证信息并拥有无状态应用程序,则即使在服务器重新启动后用户仍应保持身份验证(当然,如果在重置期间发出请求,它将失败)。

【讨论】:

  • 这在技术上是正确的,我确实应该解决一些问题,但这是一个单独的问题。当我尝试将令牌放在页面上时出现异常,这就是问题所在。
  • @Darin - 持久登录和防伪机制依赖于 &lt;machineKey/&gt; 值在 IISReset 之间保持一致。如果它们设置为 AutoGenerate,则密钥会更改,cookie 将不再有效。
  • 是的,这会在负载均衡的共享主机环境中引起严重问题。但是,我确实找到了“让我保持登录”问题的解决方案,那就是将 machineKey 放在我的 Web.config 中 - 这很好地解决了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-08
  • 1970-01-01
  • 2018-09-19
  • 2011-04-12
  • 1970-01-01
相关资源
最近更新 更多