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

发布于 2024-08-20 08:05:31 字数 2792 浏览 3 评论 0原文

我在 ASP.Net MVC 中遇到 AntiForgeryToken 问题。如果我在我的 Web 服务器上执行 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)

I’m having problems with the AntiForgeryToken in ASP.Net MVC. If I do an iisreset on my web server and a user continues with their session they get bounced to a login page. Not terrible but then the AntiForgery token blows up and the only way to get going again is to blow away the cookie on the browser.

With the beta version of version 1 it used to go wrong when reading the cookie back in for me so I used to scrub it before asking for a validation token but that was fixed when it was released.

For now I think I’ll roll back to my code that fixed the beta problem but I can’t help but think I’m missing something. Is there a simpler solution, heck should I just drop their helper and create a new one from scratch? I get the feeling that a lot of the problem is the fact that it’s tied so deeply into the old ASP.Net pipeline and is trying to kludge it into doing something it wasn’t really designed to do.

I had a look in the source code for the ASP.Net MVC 2 RC and it doesn't look like the code has changed much so while I haven't tried it, I don't think there are any answers there.

Here is the relevant part of the stack trace of the exception.

Edit: I just realised I didn't mention that this is just trying to insert the token on the GET request. This isn't the validation that occurs when you do a POST kicking off.

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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

别挽留 2024-08-27 08:05:32

现在我已经采用了一个解决方案,如果抛出异常,则会擦除 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
}

For now I've gone with a solution that scrubs the cookie if the exception is thrown. If the exception is thrown again I'll just let it happen as it was.

I won't mark this as 'the' answer for now in the hope that someone has a better answer.

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
}
迷途知返 2024-08-27 08:05:32

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

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

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

<system.web>

i had this issue and to fix what you need to do is add an explicit machine key in your web-config...

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

ensure its placed in web.config within...

<system.web>
烏雲後面有陽光 2024-08-27 08:05:32

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

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

You can add AntiForgeryConfig.SuppressIdentityHeuristicChecks = true; into global.asax

protected void Application_Start() {
    AntiForgeryConfig.SuppressIdentityHeuristicChecks = true;
}
绿光 2024-08-27 08:05:32

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

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

If I do an iisreset on my web server
and a user continues with their
session they get bounced to a login
page.

There's no reason an iisreset to bring the user to the login page. If you use cookies to track authentication information and have a stateless application, the user should stay authenticated even after a server reboot (of course if a request is made during the reset it will fail).

往日情怀 2024-08-27 08:05:32

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

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

        Response.Cookies.Clear();

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


        return View();
    }

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

Actually I found this to work in my logon action:

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

        Response.Cookies.Clear();

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


        return View();
    }

The important part was : Response.Cookies.Clear();

淡淡的优雅 2024-08-27 08:05:31

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

如果您经常看到这种情况,我建议:

  1. 配置静态 MachineKey(您应该能够在应用程序级别执行此操作),请参阅 “如何:配置 MachineKey” 了解详细信息
  2. 尽量不要在使用站点时执行 IIS 重置1

1 实现此目的的最佳方法是使用负载平衡应用程序,这将要求您设置静态 MachineKey。另一种选择是通过在网站的根目录中放置一个名为 app_offline.htm 的文件来关闭网站,这将使网站脱机并显示您的消息 - 至少用户会期望一切正常错误的。

If your MachineKey is set to AutoGenerate, then your verification tokens, etc won't survive an application restart - ASP.NET will generate a new key when it starts up, and then won't be able to decrypt the tokens correctly.

If you are seeing this a lot, I'd suggest:

  1. Configuring a static MachineKey (you should be able to do this at the application level), see "How to: Configure a MachineKey" for more information
  2. Try not to perform IIS Resets when the site is being used1

1 The best way to do this is by having a loadbalanced application, which will require you to set a static MachineKey. Another option is to take the site down by placing a file named app_offline.htm in the root of the site, which will take the site offline and display your message - at least the users will expect things to go wrong.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文