如何解决在 ASP.Net MVC 应用程序中重置 IIS 后发生的 AntiForgeryToken 异常?

45
我在ASP.Net MVC中遇到了防伪标记的问题。如果我在Web服务器上执行iisreset并且用户继续他们的会话,他们将被弹回登录页面。虽然不是很糟糕,但是防伪标记会出问题,唯一的解决方法就是清除浏览器上的cookie。
在版本1的beta版中,当我读取cookie时会出现问题,所以在请求验证令牌之前我需要清除它,但是在正式版发布后这个问题得到了解决。
目前,我认为我会返回到修复beta问题的代码,但我不能帮助自己思考是否有更简单的解决方案,难道我应该放弃他们的辅助程序并从头开始创建一个新的吗?我感觉问题的很大一部分原因是它与旧的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)

2
参见:https://dev59.com/-nM_5IYBdhLWcg3wfTE0 - Shog9
6个回答

45
如果您的 MachineKey 属性设置为 AutoGenerate,则您的验证令牌等内容将无法在应用程序重新启动后保留 - ASP.NET 会在启动时生成新的密钥,然后无法正确解密令牌。
如果您经常遇到此问题,建议:
  1. 配置静态 MachineKey(可以在应用程序层面上进行),请参阅“如何配置 MachineKey”了解更多信息。
  2. 尽量不要在使用站点时执行 IIS 重置1

1做到这一点的最佳方法是拥有负载均衡应用程序,这将要求您设置静态 MachineKey。另一个选择是通过在站点的根目录中放置名为 app_offline.htm 的文件来关闭站点,这将使站点处于离线状态并显示您的消息 - 至少用户将预计出现错误。


啊,那可能就是问题所在了。我会尝试一下,如果有效的话,我会将其标记为答案。不进行iisresets是个好主意,但我只是想确保它在现实世界中尽可能可靠地工作。 - Colin Newell
3
这里有关于这个主题的一些很好的文章:http://adam.kahtava.com/journal/2009/11/23/how-to-fix-the-validation-of-viewstate-mac-failed-error-aspnet-mvc/http://adam.kahtava.com/journal/2009/11/25/what-are-anti-cross-site-request-forgery-tokens-and-what-are-they-good-for/ - superlogical
10
我有正确的机器密钥,但有时会出现HttpAntiForgeryException异常。 :( 我不知道为什么。:/ - dariol

15

目前,我选择的解决方案是在抛出异常时清除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方法的一个重大设计缺陷,正如你的评论所建议的那样,更令人沮丧的是MVC代码是内部/密封的,因为它阻止我们轻松地修复它! - Scott Rippey
另外,ValidateAntiForgeryToken属性也有一些缺点,这让人感到沮丧,因为它是密封的。实际上,我最终获取了VAFT源代码,取消了其密封性,并创建了自己的子类,可以应用于整个控制器并支持JSON请求。 - Scott Rippey
如果要支持MVC3,我会更改的一件事是你的静态帮助器扩展方法应该返回MvcHtmlString,因为helper.AntiForgeryToken(string, string, string)返回该类型而不是普通字符串。 - Thiago Silva

11

我遇到了这个问题,需要解决的方法是在您的web-config中添加一个明确的机器密钥...

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

确保将其放置在web.config文件中...

<system.web>

1
给定的链接无法使用。请使用以下网址[http://www.allkeysgenerator.com/Random/ASP-Net-MachineKey-Generator.aspx]。 - Optimus
2
我可能有点偏执,但我不会使用任何互联网网站为我生成密钥。较新版本的IIS可以实现此功能,您还可以使用.NET系统类RNGCryptoServiceProvider来生成密钥。 - Menahem

7
您可以在global.asax中添加AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;以防止身份启发式检查。
protected void Application_Start() {
    AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
}

2
那到底是做什么的? - Timothy Groote
@TimothyGroote 来自http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages: 一个布尔值,它决定了是否应该停用反 XSRF 系统对基于声明的身份的支持。如果该值为真,则系统将假定 IIdentity.Name 适合用作唯一的每个用户标识符,并且不会像在 WIF / ACS / 基于声明的身份验证部分中所描述的那样特殊处理 IClaimsIdentity 或 ClClaimsIdentity。默认值为 false。 这需要 IIdentity.Name 是足够唯一的标识符以保证安全。 - RJ Cuthbertson

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之后被弹回登录页面。它还可以防止由于iisreset而使cookie失效。 - Colin Newell
2
我也有一个静态的机器密钥,但是遇到了同样的问题。 - Jeff
1
尝试在已登录时登录不应该通过注销用户来惩罚。如果用户无意中打开了多个窗口并意外地在旧的登录页面上操作,用户可能会失去其他窗口中的工作/状态。更好的解决方案是将他们转发到登录屏幕,就像Zia将他们转发到防伪页面一样。 - sshine

4

如果我在我的Web服务器上执行iisreset, 用户继续他们的会话,他们将被弹回到登录页面。

没有理由让iisreset将用户带到登录页面。如果您使用cookie来跟踪身份验证信息并且有一个无状态应用程序,则用户应该在服务器重新启动后保持已验证状态(当然,如果在重置期间进行请求,则会失败)。


1
那在技术上是正确的,而且我真的应该解决这个问题,但那是一个独立的问题。当我尝试将令牌放在页面上时出现异常的事实才是问题所在。 - Colin Newell
1
@Darin - 持久登录和防伪造机制依赖于IISResets中<machineKey/>值的一致性。 如果它们设置为AutoGenerate,则密钥会更改并且Cookie将不再有效。 - Kev
是的,在一个负载均衡很重的共享托管环境中,这会导致重大问题。然而,我找到了一个解决“保持登录状态”的方法,那就是将machineKey放置在我的Web.config文件中 - 这样修复得很好。 - Mike Perrenoud

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