解决防伪令牌问题

Posted

技术标签:

【中文标题】解决防伪令牌问题【英文标题】:Troubleshooting anti-forgery token problems 【发布时间】:2011-08-11 16:27:05 【问题描述】:

我有一个表单帖子一直给我一个防伪令牌错误。

这是我的表格:

@using (html.BeginForm())

    @Html.AntiForgeryToken()
    @Html.EditorFor(m => m.Email)
    @Html.EditorFor(m => m.Birthday)
    <p>
        <input type="submit" id="Go" value="Go" />
    </p>

这是我的操作方法:

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Join(JoinViewModel model)

    //a bunch of stuff here but it doesn't matter because it's not making it here

这是 web.config 中的 machineKey:

<system.web>
  <machineKey validationKey="mykey" decryptionKey="myotherkey" validation="SHA1" decryption="AES" />
</system.web>

这是我得到的错误:

A required anti-forgery token was not supplied or was invalid.

我已经读到在 HttpContext 上更改用户将使令牌无效,但这里不会发生这种情况。我的 Join 操作上的 HttpGet 只返回视图:

[HttpGet]
public ActionResult Join()

    return this.View();

所以我不确定发生了什么。我四处搜索,一切似乎都表明它是 machineKey 更改(应用程序周期)或用户/会话更改。

还有什么可能发生的?我该如何解决这个问题?

【问题讨论】:

这个页面在 18 个月内被浏览了近 4000 次,没有人知道你需要做的只是双击登录按钮来复制它? Prevent double submission of forms in jQuery的可能重复 双重发布是触发防伪令牌异常的一种方式。正如您从下面的代码中看到的那样,有很多不同的场景可以抛出这个,特别是在我的例子中,它与重复发布无关。 【参考方案1】:

我不知道您的意思是您能够按需获取错误 - 或者您在日志中看到它,但无论如何,这是一种保证防伪令牌错误的方法。

等等……

确保您已注销,然后输入您的登录名 双击登录按钮 你会得到:

提供的防伪令牌用于用户“”,但当前用户是“XXX@yahoo.com”。

(现在我将假设这个确切的错误消息在 MVC4 中发生了变化,并且这基本上与您收到的消息相同)。

那里有很多人仍然双击所有内容 - 这很糟糕!我刚醒来后才发现这一点,所以我真的不知道这是如何通过测试的。您甚至不必双击 - 如果按钮无响应,当我再次单击时,我自己也会遇到此错误。

我刚刚删除了验证属性。我的网站始终是 SSL,我并不太担心风险。我只需要它现在就可以工作。另一种解决方案是使用 javascript 禁用按钮。

这可以在 MVC4 初始安装模板上复制。

【讨论】:

如果有人发现与此问题相关的 Connect 问题 - 请在 cmets 中发布 我使用此代码防止此类问题 $('#loginForm form').submit(function () $(this).find('input[type=submit]').attr ("禁用","禁用"); ); @Bohdan 你应该让你的评论成为答案。我知道是什么导致了我的问题(用户双击),但正在寻找快速解决方案。你的 JS 是一种享受 @WillD 在 MVC 5 中未修复或修改?我已经升级了,但还没有机会检查 @Simon_Weaver 我正在使用 MVC5,双击仍然会抛出此错误【参考方案2】:

在 Adam 的帮助下,我将 MVC 源添加到我的项目中,并且能够看到有很多情况会导致相同的错误。

这是用于验证防伪令牌的方法:

    public void Validate(HttpContextBase context, string salt) 
        Debug.Assert(context != null);

        string fieldName = AntiForgeryData.GetAntiForgeryTokenName(null);
        string cookieName = AntiForgeryData.GetAntiForgeryTokenName(context.Request.ApplicationPath);

        HttpCookie cookie = context.Request.Cookies[cookieName];
        if (cookie == null || String.IsNullOrEmpty(cookie.Value)) 
            // error: cookie token is missing
            throw CreateValidationException();
        
        AntiForgeryData cookieToken = Serializer.Deserialize(cookie.Value);

        string formValue = context.Request.Form[fieldName];
        if (String.IsNullOrEmpty(formValue)) 
            // error: form token is missing
            throw CreateValidationException();
        
        AntiForgeryData formToken = Serializer.Deserialize(formValue);

        if (!String.Equals(cookieToken.Value, formToken.Value, StringComparison.Ordinal)) 
            // error: form token does not match cookie token
            throw CreateValidationException();
        

        string currentUsername = AntiForgeryData.GetUsername(context.User);
        if (!String.Equals(formToken.Username, currentUsername, StringComparison.OrdinalIgnoreCase)) 
            // error: form token is not valid for this user
            // (don't care about cookie token)
            throw CreateValidationException();
        

        if (!String.Equals(salt ?? String.Empty, formToken.Salt, StringComparison.Ordinal)) 
            // error: custom validation failed
            throw CreateValidationException();
        
    

我的问题是它将身份用户名与表单令牌的用户名进行比较的情况。就我而言,我没有设置用户名(一个为空,另一个为空字符串)。

虽然我怀疑很多人会遇到同样的情况,但希望其他人会发现查看正在检查的基本条件很有用。

【讨论】:

btw.. 你是怎么得到空字符串和空用户名的场景的? 有点奇怪的情况,这就是为什么很难排除故障的原因。但是我将自定义身份设置为使用用户电子邮件作为用户名。后来,我在注册时不需要电子邮件的地方做了它(Twitter API 不提供电子邮件),所以在这些情况下,电子邮件为空。我认为表单用户名是空的,并且将其与 null Identity.UserName 进行比较,它失败了。我忘记了身份依赖于电子邮件,所以现在我只是让它使用用户 ID。再次感谢您的帮助。 我遇到了同样的问题...使用 Windows Identity Foundation...并在博客中介绍了它。非常感谢关于从哪里开始寻找的提示......我会说这是 MVC 中的一个错误(虽然是一个简单的错误......)erikbra.wordpress.com/2011/08/10/… 这个验证功能,特别是抛出的异常,在 MVC5 中是否已更新为具有更多描述性错误,以帮助管理员了解根本原因来自何处?在我看来,对 5 种不同场景做出通用响应并不是好的架构/编码。 哪个类包含这个方法?【参考方案3】:

AntiForgeryToken 还会检查您登录的用户凭据是否未更改——这些凭据也在 cookie 中进行了加密。您可以通过在 global.asax.cs 文件中设置 AntiForgeryConfig.SuppressIdentityHeuristicChecks = true 来关闭它。

【讨论】:

对我来说不是一个解决方案......我试过了,但它不起作用 设置这个有什么安全隐患?【参考方案4】:

您应该防止重复提交表单。 我使用这样的代码来防止此类问题:

$('#loginForm').on('submit',function(e)
    var $form = $(this);

    if (!$form.data('submitted') && $form.valid()) 
      // mark it so that the next submit can be ignored
      $form.data('submitted', true);
      return;
    

    // form is invalid or previously submitted - skip submit
    e.preventDefault();
);

$('#loginForm').submit(function () 
    $(this).find(':submit').attr('disabled', 'disabled'); 
);

【讨论】:

这很糟糕:如果您有客户端 javascript 表单验证,即使表单无效,您的代码也会禁用提交按钮,这意味着用户无法修复客户端错误并重新提交。 您可以在禁用提交按钮之前使用jQuery .valid() 方法检查表单是否有效。 在我写完我的 JS 版本后看到了这个答案。这应该更好:$("form").on("submit", function (e) var self = $(this); if (!self[0].checkValidity() || self.data("submitted")) e.preventDefault(); return; self.data("submitted", true); ); 这个答案似乎对我有用,直到我意识到通过客户端验证,即使字段错误并且没有真正提交,也认为提交的表单。【参考方案5】:

我刚刚遇到了一个问题:@Html.AntiForgeryToken() 被调用了两次,因此防伪造令牌在 HTTP Post 有效负载中被搞砸了。

【讨论】:

【参考方案6】:

您是在一台服务器上还是在网络场上?如果是单个服务器,请在 web.config 中注释掉您的 machineKey 元素,然后作为基本起点重试。任何改变? 另外——你能想到你的cookies会被清除或过期的任何原因吗——它们也是正常工作所必需的。

【讨论】:

我在 Mac 上的 Windows 虚拟机上运行它。但我停留在虚拟中。该服务器是由 Visual Studio (2010) 启动的开发服务器。我还使用主机标头记录将“真实” URL 路由到我的本地框(即,使实时 URL 指向我的本地)。关于machineKey,其实我一开始是没有这个的,后来遇到问题后才加上。所以它是双向的。 检查 cookie 是否有效,并将其发送到正确域的服务器。使用 fiddler 检查 cookie 域是否正确。你是否一直呆在这个主机地址中(并且永远不会切换回 localhost 而不是 yoursite.com - 甚至对于任何 ajax 函数都不会)。令牌不依赖于会话,因此会话结束无关紧要。您可以获取 mvc 源并进入该方法以验证它失败的位置。 谢谢亚当,我会这样做的。我会说这涉及到与 Twitter 的一些交互。基本上,我有 (me) TwitterAuthorize -> (twitter) Twitter 的授权 -> (me) TwitterCallback -> (me) Join。但是调用这个页面的页面在我的服务器上,所以我不认为这个远程 Twitter 调用是一个因素。 更新 - 现在看来问题是特定于我的第二个测试帐户的。出于某种原因,我的第一个测试帐户可以正常发布表单,但我的第二个帐户始终未能通过防伪令牌验证。检查 cookie,两个帐户上的 cookie 看起来几乎相同,相同数量的 cookie、相同的名称和相同的域。当然是不同的值。 Doh - 下一步(至少对我而言)将是 MVC 调试到源代码以查看失败以及失败的原因、值等。代码相当简单 - 看看这个设置它的帖子:weblogs.asp.net/gunnarpeipman/archive/2010/07/04/…【参考方案7】:

在禁用提交按钮之前,有必要检查表单是否有效。

<script type="text/javascript">
//prevent double form submission
$('form', '#loginForm').submit(function () 
    if ($(this).valid()) 
        $(this).find(':submit').attr('disabled', 'disabled');
    
);

【讨论】:

【参考方案8】:

另一个可能导致我出现此错误的原因是:我的一个表单中有两个 @Html.AntiForgeryToken()

一旦删除,问题就消失了。

【讨论】:

【参考方案9】:

当我不得不将我的应用程序迁移到新机器时,我也遇到了同样的问题。不明白为什么会突然出现这个错误,但感觉肯定是和迁移有关,于是开始研究通过aspnet_reqsql注册数据库的问题,排除后才知道一定是和应用注册有关果然我在类似的地方找到了答案。我还在旅途中发现有两种方法可以解决这个问题。

    ASP.NET 自动为每个应用程序生成一个加密密钥,并将该密钥存储在 HKCU 注册表配置单元中。在服务器场上迁移或访问应用程序时,这些密钥不匹配,因此方法一是向 web.config 添加唯一的机器密钥。机器密钥可以从 IIS 管理控制台生成并添加到您的 web.config 部分

    第二种方法是通过 appPool 使用 AspNet_RegIIS 和开关 -ga 为工作进程授予对 HKCU 注册表的访问权限,或者使用 -i 为所有应用授予访问权限

aspnet_regiis -ga "IIS APPPOOL\app-pool-name"

无论您选择哪种方法都应该可以解决此问题,但在我看来,未来迁移和服务器更改最可靠的方法将是 web.config 中的唯一键,请记住这将导致应用程序覆盖HKCU 注册表配置单元,让您的应用程序保持运行。

【讨论】:

【参考方案10】:

我刚刚遇到了类似的问题。我得到了:

The required anti-forgery form field "__RequestVerificationToken" is not present. 

有趣的是我尝试调试它并在我希望在控制器中找到它的两个地方都看到了令牌

var formField = HttpContext.Request.Params["__RequestVerificationToken"];
var cookie = System.Web.HttpContext.Current.Request.Cookies["__RequestVerificationToken"].Value;

实际上我应该一直在这里寻找:

var formField = HttpContext.Request.Form["__RequestVerificationToken"];

由于 Params 不仅包含表单字段,它还包含 QueryString、Form、Cookie 和 ServerVariables。

当红鲱鱼消失后,我发现 AntiForgeryToken 的格式有误!

【讨论】:

【参考方案11】:

html 错误记录器的行不正确。

您必须检查页面中的所有加载值不为空。

【讨论】:

以上是关于解决防伪令牌问题的主要内容,如果未能解决你的问题,请参考以下文章

JWT 和防伪令牌

登录页面上是不是需要防伪令牌?

如何使用防伪令牌攻击后期数据

Asp.net Mvc:Jquery post 数组 + 防伪令牌

带有asp .net核心的防伪令牌jquery ajax

即使没有明确的 [AutoValidateAntiforgeryToken],是不是也会自动添加防伪令牌?