Asp.net mvc 为什么即使使用静态机器密钥,HttpAntiForgeryException也会随机发生?

Asp.net mvc 为什么即使使用静态机器密钥,HttpAntiForgeryException也会随机发生?,asp.net-mvc,exception,azure,antiforgerytoken,Asp.net Mvc,Exception,Azure,Antiforgerytoken,我们有一个ASP.NET MVC 2(.NET 4)应用程序运行在Windows Azure(最新2.x操作系统版本)上,有两个web角色实例 我们对所有POST请求使用MVC提供的防伪令牌,并且在web.config中设置了一个静态机器密钥,因此所有操作都可以在多台机器上工作,并且可以跨重启。99.9%的情况下,它工作完美 然而,我们时不时会记录一个HttpAntiForgeryException,并显示消息“未提供所需的防伪令牌或该令牌无效” 我知道问题可能是浏览器中不允许使用Cookie,

我们有一个ASP.NET MVC 2(.NET 4)应用程序运行在Windows Azure(最新2.x操作系统版本)上,有两个web角色实例

我们对所有POST请求使用MVC提供的防伪令牌,并且在web.config中设置了一个静态机器密钥,因此所有操作都可以在多台机器上工作,并且可以跨重启。99.9%的情况下,它工作完美

然而,我们时不时会记录一个HttpAntiForgeryException,并显示消息“未提供所需的防伪令牌或该令牌无效”

我知道问题可能是浏览器中不允许使用Cookie,但我们已验证和Cookie是否已启用并正确来回发送

该错误在各种浏览器中都会发生,并且显然会给用户带来问题,因为他们必须重复该操作,否则可能会丢失一些数据。只需说一句,我们还无法在本地重现这个问题,但它只发生在WindowsAzure上


为什么会这样?我们怎样才能避免它呢?

有几个选项可以供您尝试。您可以尝试远程访问计算机并查看事件日志,以查看是否可以从中获得有关发生此事件的位置的更多信息。如果这没有帮助,您可以使用DebugDiag或其他工具捕获进程的转储(DebugDiag将允许您在发生此特定异常时捕获一个转储)。然后看看这是怎么回事


如果您似乎无法从中找到答案,您可以随时与Microsoft一起创建支持案例,以帮助您进行调查。

防伪令牌在发出时包含当前连接用户的用户名。在验证其有效性时,将对照令牌发出时使用的用户检查当前连接的用户。因此,例如,如果您有一个表单,其中的用户尚未经过身份验证,并且您发出了一个防伪令牌,那么表单中不会存储任何用户名。如果在提交表单时对用户进行身份验证,则令牌将不再有效。这同样适用于注销

下面是验证方法的外观:

public void Validate(HttpContextBase context, string salt)
{
    string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
    string str2 = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);
    HttpCookie cookie = context.Request.Cookies[str2];
    if ((cookie == null) || string.IsNullOrEmpty(cookie.Value))
    {
        throw CreateValidationException();
    }
    AntiForgeryData data = this.Serializer.Deserialize(cookie.Value);
    string str3 = context.Request.Form[antiForgeryTokenName];
    if (string.IsNullOrEmpty(str3))
    {
        throw CreateValidationException();
    }
    AntiForgeryData data2 = this.Serializer.Deserialize(str3);
    if (!string.Equals(data.Value, data2.Value, StringComparison.Ordinal))
    {
        throw CreateValidationException();
    }
    string username = AntiForgeryData.GetUsername(context.User);
    if (!string.Equals(data2.Username, username, StringComparison.OrdinalIgnoreCase))
    {
        throw CreateValidationException();
    }
    if (!string.Equals(salt ?? string.Empty, data2.Salt, StringComparison.Ordinal))
    {
        throw CreateValidationException();
    }
}

调试此问题的一种可能方法是从源代码重新编译ASP.NET MVC,并准确记录引发异常时输入的if情况。

我最近也遇到过此问题,并找到了两个原因

1。浏览器为缓存的页面恢复打开时的最后一个会话

如果您有一个可缓存的页面,可以向您的服务器执行post(即,antiforgery将打开),并且用户的浏览器设置为在启动时恢复最后一个会话(chrome中存在此选项),则该页面将从缓存中呈现。但是,请求验证cookie将不存在,因为它是浏览器会话cookie,并且在浏览器关闭时被丢弃。因为cookie不见了,所以你得到了防伪例外。解决方案:返回响应头,这样页面就不会被缓存(即缓存控制:private,no store)

2。在站点启动时打开多个选项卡时的竞争条件


浏览器可以选择在启动时打开一组选项卡。如果其中有多个访问了返回请求验证cookie的站点,则您可能会遇到请求验证cookie被覆盖的竞争条件。发生这种情况的原因是,来自未设置请求验证cookie的用户的多个请求击中了您的服务器。处理第一个请求并设置请求验证cookie。接下来处理第二个请求,但它没有发送cookie(请求时尚未设置),因此服务器生成一个新的cookie。新页面覆盖了第一个页面,现在该页面在下一次执行post时将获得反伪造请求异常。MVC框架不能处理这种情况。这个错误已经报告给了微软的MVC团队。

我有一些MVC3网络应用程序也经常收到这个错误。大多数是因为客户不发送帖子正文。其中大多数是IE8,因为在常规表单post之前的ajax请求存在一些错误。IE有一个热修复程序似乎可以解决这些症状,这在某种程度上证明了在这些情况下它是一个客户端错误

网络上有一些关于这个问题的讨论,虽然没有什么太有用的,但我绝对不想弄乱keep alive Timeout,这在某些地方是一个建议的“解决方案”

我从来没有能够通过各种尝试来重现它,以重置帖子之间的连接,所以我恐怕没有一个真正的解决方案来解决IE空帖子主体的问题。我们减轻了一点风险的方法是确保在通过ajax检索数据时从不使用POST方法

如果您记录完整的请求,请检查帖子正文是否为空,如果为空,则可能是较旧的IE。我不是说内容长度:0,它的内容长度通常在标题中看起来是正确的,但在请求的标题后实际上什么都没有


尽管如此,这个问题对我来说仍然是个谜,因为我们仍然偶尔会遇到一个例外,那里有一个完整的帖子主体。我们的用户名永远不会改变,我们的密钥也是静态的,我还没有尝试将调试添加到源代码中,如果有机会,我会报告我的发现。

我在自制的防伪代码中遇到了类似的问题,这在概念上与MVC机制非常相似。大部分问题似乎是因为现代浏览器似乎愿意显示指定为非缓存页面的缓存副本

我尝试了所有无页面缓存指令的组合,但有时仍会显示缓存页面

我发现更好的解决方案是挂接页面的onbeforeunload事件并显式清除va
window.location.reload(true);