我们在Windows Azure(最新的2.x操作系统版本)上运行了一个ASP.NET MVC 2(.NET 4)应用程序,其中包含两个Web角色实例。
我们使用MVC提供的防伪令牌来处理所有POST请求,并且我们在web.config中设置了一个静态机器密钥,因此一切都可以在多台计算机上运行并重新启动。 99.9%的案例完美运作。
然而,我们偶尔会记录一个HttpAntiForgeryException,并显示消息“未提供所需的防伪标记或无效。”
我知道问题可能是浏览器中不允许使用Cookie,但我们已经验证了这些问题并启用了Cookie并正确地来回发送。
错误发生在各种浏览器上,显然会给用户带来问题,因为他们必须重复操作,否则会丢失一些数据。可以这么说,我们无法在本地重现问题,但它只发生在Windows Azure上。
为什么会这样?我们怎样才能避免呢?
答案 0 :(得分:17)
我最近遇到了这个问题,并找到了两个原因。
<强> 1。浏览器会在已打开的页面上恢复上次会话
如果您有一个可缓存的页面可以对您的服务器执行帖子(即防伪程序将打开),并且用户将其浏览器设置为在启动时恢复上一个会话(此选项存在于chrome中),该页面将为从缓存中呈现。但是,请求验证cookie不会存在,因为它是浏览器会话cookie,并在浏览器关闭时被丢弃。由于cookie消失了,你会得到防伪例外。解决方案:返回响应头,以便不缓存页面(即Cache-Control:private,no-store)。
<强> 2。如果在您的网站启动时打开多个标签,则会出现竞争情况
浏览器可以选择在启动时打开一组标签。如果其中多个网站点击了您的网站,并返回了请求验证Cookie,那么您可能会遇到请求验证Cookie被覆盖的竞争条件。发生这种情况是因为多个请求从没有设置请求验证cookie的用户访问您的服务器。处理第一个请求并设置请求验证cookie。接下来处理第二个请求,但它没有发送cookie(在请求时尚未设置),因此服务器生成一个新请求。新的一个覆盖了第一个,现在该页面将在下一次执行帖子时获得防伪请求异常。 MVC框架不处理这种情况。已向Microsoft的MVC团队报告此错误。
答案 1 :(得分:7)
防伪令牌包含当前连接用户的用户名。在验证其有效性时,将检查当前连接的用户与发出令牌时使用的用户。因此,例如,如果您有一个表单,其中用户尚未经过身份验证并且您发出了防伪令牌,则不会存储任何用户名。如果您在提交表单时对用户进行身份验证,则令牌将不再有效。同样适用于退出。
以下是Validate方法的样子:
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情况。
答案 2 :(得分:1)
我有一些MVC3网络应用也可以定期获得。其中大多数是因为客户端没有发送POST正文。其中大部分都是IE8,因为在常规表单帖子之前有一些ajax请求的错误。有一个IE的修补程序似乎解决了症状,这证明在这些情况下它是一个客户端错误
http://support.microsoft.com/?kbid=831167
关于网络上的问题有一些讨论,虽然没有什么太有用,但我绝对不会搞乱保持活动超时,这在某些地方是一个建议的“解决方案”......
https://www.google.com/search?q=ie8+empty+post+body
我从来没有能够通过各种尝试来重置它来重置POSTS之间的连接,所以我担心我没有真正解决IE空机身的问题。我们稍微减轻它的方式是确保在通过ajax检索数据时我们从不使用POST方法。
如果您记录完整的请求,请检查POST正文是否为空,如果是,则可能是较旧的IE。我并不是指Content-Length:0,它通常会在标题中看起来正确的Content-Length,但在请求中的标题之后几乎没有任何内容。
整个问题对我来说仍然是一个谜,因为我们仍然偶尔会遇到一个完整的POST主体。我们的用户名永远不会改变,我们的密钥也是静态的,我没有尝试过向源代码添加调试,如果我能解决这个问题,我会报告我的发现。
答案 3 :(得分:0)
您可以尝试一些选项。您可以尝试远程登录到计算机并查看事件日志,看看是否可以从中获取有关这种情况的更多信息。如果这没有帮助,您可以使用DebugDiag或其他工具来捕获进程的转储(DebugDiag将允许您在此特定异常时捕获一个)。然后看看那是为了看看发生了什么。
如果您似乎无法从中找到答案,您可以随时与Microsoft一起创建支持案例,以帮助您进行调查。
答案 4 :(得分:0)
我遇到了类似家庭酿造的防伪代码的问题,这在概念上与MVC机制非常相似。大多数问题似乎发生,因为现代浏览器似乎愿意显示指定为非缓存的页面的缓存副本。
我已尝试过所有页面no-cache指令的组合,但有时我仍会显示缓存页面。
我发现更好的解决方案是挂钩页面的onbeforeunload事件,并明确清除在DOM中保存令牌值的隐藏输入字段的值。
如果加载了页面的缓存副本,则它似乎包含已清除的输入字段值。然后我在文档就绪函数中对此进行测试,并在必要时重新加载页面:
window.location.reload(true);
似乎非常有效,我怀疑它可能也适用于MVC反伪造代码。