显然是随机错误:“防伪令牌验证失败。防伪 cookie 令牌和请求令牌不匹配。”

Posted

技术标签:

【中文标题】显然是随机错误:“防伪令牌验证失败。防伪 cookie 令牌和请求令牌不匹配。”【英文标题】:Apparently Random Error: "Antiforgery token validation failed. The antiforgery cookie token and request token do not match." 【发布时间】:2018-06-19 04:03:00 【问题描述】:

背景

我有一个相对较新的 ASP.NET Core 2 站点。它仅在一台服务器上运行(Windows Server 2012 R2、IIS 8.5),而且我每隔几天才在上传更新时重新启动站点一次。大约每天一次,用户的请求由于被防伪系统拒绝而失败。这些是 POST 请求,它们并没有什么特别之处。我在 POST 请求中包含了防伪值,并且 99% 的时间,POST 请求都有效。但是当他们不这样做时,标准输出日志会显示,“防伪令牌验证失败。防伪 cookie 令牌和请求令牌不匹配。”当我使用该确切语句执行 Web 搜索时,我得到的结果为零。所以我转向了 Stack Overflow。 [这不再是真的,因为现在 Web 搜索会产生这个 Stack Overflow 问题。]

错误

我在下面包含了标准输出日志的相关部分。

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 POST [domain redacted] application/x-www-form-urlencoded 234
info: Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter[1]
      Antiforgery token validation failed. The antiforgery cookie token and request token do not match.
Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The antiforgery cookie token and request token do not match.
   at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.ValidateTokens(HttpContext httpContext, AntiforgeryTokenSet antiforgeryTokenSet)
   at Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery.<ValidateRequestAsync>d__9.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter.<OnAuthorizationAsync>d__3.MoveNext()
info: Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker[3]
      Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.AutoValidateAntiforgeryTokenAuthorizationFilter'.
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
      Executing HttpStatusCodeResult, setting HTTP status code 400
info: Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker[2]
      Executed action /Index in 2.6224ms
warn: Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery[1]
      Antiforgery validation failed with message 'The antiforgery cookie token and request token do not match.'.

对于导致上述标准输出输出的请求,IAntiforgery.IsRequestValidAsync 通过返回 false 表示同意。请注意错误消息“防伪 cookie 令牌和请求令牌不匹配”。这是失败的 POST 请求和相关 cookie 的简化示例。

POST: __RequestVerificationToken= CfDJ8F9Fs4CqDFpLttT96eZw9WHjWfHO8Yawn35k4Yq3gDK5n1TDJDDiY5o86VQs1_qOVIYBydCizBU4knb7Jmq1-heGhwnMu2KmhUIiAd0xI7Sudv3GX-J0OI6wRfiPL4L1KRs2Pml8dbsDfwemewBqi18

Cookie: .AspNetCore.Antiforgery.ClRyCRmWApY=CfDJ8F9Fs4CqDFpLttT96eZw9WFtJht41WcNrmgshi2pFGwcxhr0_0hvINQc7Yl9Cbjhv-TiSNXeEctyKborLI49AcjHfWIgOmmKkbjOe7QMn8Z0WZtkQy5JcaBHKEGTu1p-La8JL8pZZqZy02Hrswpkh3I

在请求失败并出现 400 错误(使用一些错误处理中间件)后,我还捕获了几次此数据:

AntiforgeryTokenSet tokens = antiforgery.GetTokens(context);
tokens.CookieToken:  null
tokens.FormFieldName:  "__RequestVerificationToken"
tokens.HeaderName:  "RequestVerificationToken"
tokens.RequestToken:  "CfDJ8F9Fs4CqDFpLttT96eZw9WH33jSw5mM8h7RpEd3vGISQTRkx1rfwm-L2lfkvXKMBc-riESmoTo_fnIjeBbRmOo5KuJHr09f8B75sQ9g_djIVeeaGwMw5KE6W1O2-7Vi03fCnwlTv8l-BWGst76Ln-ZQ"

所以这里是三个字符串:

POST String:  "CfDJ8F9Fs4CqDFpLttT96eZw9WHjWfHO8Yawn35k4Yq3gDK5n1TDJDDiY5o86VQs1_qOVIYBydCizBU4knb7Jmq1-heGhwnMu2KmhUIiAd0xI7Sudv3GX-J0OI6wRfiPL4L1KRs2Pml8dbsDfwemewBqi18"
Cookie String:  "CfDJ8F9Fs4CqDFpLttT96eZw9WFtJht41WcNrmgshi2pFGwcxhr0_0hvINQc7Yl9Cbjhv-TiSNXeEctyKborLI49AcjHfWIgOmmKkbjOe7QMn8Z0WZtkQy5JcaBHKEGTu1p-La8JL8pZZqZy02Hrswpkh3I"
antiforgery.GetTokens(context).RequestToken:  "CfDJ8F9Fs4CqDFpLttT96eZw9WH33jSw5mM8h7RpEd3vGISQTRkx1rfwm-L2lfkvXKMBc-riESmoTo_fnIjeBbRmOo5KuJHr09f8B75sQ9g_djIVeeaGwMw5KE6W1O2-7Vi03fCnwlTv8l-BWGst76Ln-ZQ"

POST 字符串和 cookie 字符串不匹配,但根据我的经验,即使是 ASP.NET Core 认为合法的请求,它们也不会匹配。但奇怪的是,POST 字符串和tokens.RequestToken 也不匹配。我认为它们应该匹配,尽管我在请求生命周期的后期捕获了tokens.RequestToken,所以也许这与它有关。

GitHub 上的 ASP.NET Core 2

我决定看一下 ASP.NET Core 2 的源代码。我找到了这个文件,尤其是第 145 行:

https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Internal/DefaultAntiforgeryTokenGenerator.cs

该行收到消息“防伪 cookie 令牌和请求令牌不匹配”。从此文件第 134 行:

https://github.com/aspnet/Antiforgery/blob/dev/src/Microsoft.AspNetCore.Antiforgery/Resources.resx

所以我认为这就是消息的来源,但我仍然想知道为什么会发生这种情况。

问题

有人能帮我弄清楚为什么这些防伪令牌没有验证吗?用户的 Web 浏览器是否有可能破坏 cookie 或 POST 数据?有没有人在这方面有经验或有什么建议?谢谢。

【问题讨论】:

该 cookie 的超时时间是多少?也许用户只是等待很长时间?人们倾向于保持页面打开数小时。也可能是在最坏的时候清理数据库中的“有效防伪令牌”。 谢谢你,@Christopher。我不知道 cookie 的超时时间。它由 ASP.NET Core 设置。但是cookie是存在的。只是防伪系统不能令人满意。此外,我可以看到用户在页面加载几秒钟后提交表单的情况。所以我认为这与超时无关。我不知道你在说什么清理数据库。我将它们存储在内存中(据我所知,ASP.NET Core 的默认设置)。由于站点没有重新启动,因此存储的值应该仍然完好无损。 @user1325179 我看到的东西非常相似。你有没有弄清楚问题是什么? @GaryBrunton,没有。我仍在为此苦苦挣扎。几天前,我将它发布到了 ASP.NET Core GitHub 问题页面,但这还没有让我有所收获:github.com/aspnet/Home/issues/2882。 @user1325179 我们能够追踪我们的问题。我们不知道防伪令牌会考虑用户是否经过身份验证。因此,在我们的案例中,我们发现经过身份验证的用户会打开多个包含表单(和防伪令牌)的浏览器选项卡。最终,他们将从其中一个标签中注销。注销后,如果他们要返回包含表单的其他选项卡之一并尝试提交,则会引发此错误,因为生成令牌时假设用户已通过身份验证。我不确定这是否对您有帮助。 【参考方案1】:

全局禁用过滤器似乎是关闭它的唯一方法。我得到了@svallis 的代码来进行视线修改:

services.AddMvc().AddRazorPagesOptions(options =>

    options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
);

https://github.com/aspnet/Mvc/issues/7012

【讨论】:

谢谢,但这不能满足我的要求。我想使用防伪系统,而不是禁用它。 @jaybro,我没有解决它。几个月前,我在 GitHub 上打开了 Microsoft 的问题。他们还没有给出有意义的回应,问题仍然悬而未决。 @user1325179 好的,谢谢,我发现我的问题与使用令牌的同一视图上的两个表单有关,我通过 asp-antiforgery 属性禁用了一个表单,另一个(重要)表单禁用了开始工作了。【参考方案2】:

我在这里找到了解决方案:https://github.com/aspnet/Antiforgery/issues/116

using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.html;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;


// fix from https://github.com/aspnet/Antiforgery/issues/116
namespace WebAppCore.Code

    public class HtmlGeneratorNoStoreAntiforgery: DefaultHtmlGenerator  
    
        public HtmlGeneratorNoStoreAntiforgery(
            IAntiforgery antiforgery,
            IOptions<MvcViewOptions> optionsAccessor,
            IModelMetadataProvider metadataProvider,
            IUrlHelperFactory urlHelperFactory,
            HtmlEncoder htmlEncoder,
            ValidationHtmlAttributeProvider validationAttributeProvider)
            : base(antiforgery, optionsAccessor, metadataProvider, urlHelperFactory, htmlEncoder, validationAttributeProvider)
        
        

        public override IHtmlContent GenerateAntiforgery(ViewContext viewContext)
        
            var result = base.GenerateAntiforgery(viewContext);

            viewContext
                    .HttpContext
                    .Response
                    .Headers[HeaderNames.CacheControl]
                = "no-cache, max-age=0, must-revalidate, no-store";

            return result;
        
    

并添加startup.cs:

services.AddSingleton<Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator, HtmlGeneratorNoStoreAntiforgery>();

【讨论】:

【参考方案3】:

关于 CookieToken 是否为空:可能该请求是伪造的?由于 cookie 丢失,我怀疑您的网站确实在每次请求时都会发送它。什么时候可以丢失?当它来自其他地方时。

关于其他问题:

    您的中间位置是不是在Configure 方法的身份验证部分之后将AntiforgeryToken 作为cookie 添加到响应中? 您是否有可能在多个 GET 请求中返回多个 AntiforgeryToken?在这种情况下,可能会出现竞争条件,即哪个请求最晚返回(浏览器将使用什么),哪个请求最后离开服务器;这些可能不同 -> 令牌不匹配。

您应该在第一次 Get 操作时只返回一个。当您在一个实例上托管前端和后端时,它可能是根 (/),如果您在另一个端口上使用 API,则应确保在 POST、PUT 或 PATCH 之前执行 GET。

一个实例启动示例:

builder.Use(next => context =>

    var path = context.Request.Path.Value;
    if (!string.Equals(path, "/", StringComparison.OrdinalIgnoreCase) ||
        context.Request.Method != "GET") return next(context);
    var tokens = antiforgery.GetAndStoreTokens(context);
    context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
        new CookieOptions  HttpOnly = false, Path = "/" );

    return next(context);
);

多个端口实例(注意 CORS)

public IServiceProvider ConfigureServices(IServiceCollection services)

  services.AddCors(options =>
  
     options.AddDefaultPolicy(builder => builder
            .WithOrigins(/*allowed domains here*/)
            .AllowAnyHeader()
            .WithMethods("GET", "POST", "PUT", "DELETE")
            .AllowCredentials()
            .Build()
     );
  );

public virtual void Configure(IApplicationBuilder app, IAntiforgery antiforgery)

  app.UseAuthentication()
     .UseAuthorization()
     .Use(next => context =>
      
         var path = context.Request.Path.Value;
         if (!string.Equals(path, "/api/settings", StringComparison.OrdinalIgnoreCase) ||
             context.Request.Method != "GET") return next(context);
         var tokens = antiforgery.GetAndStoreTokens(context);
         context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken,
             new CookieOptions  HttpOnly = false );

         return next(context);
      );

请记住,如果您将前端托管在另一个端口/位置,则应为 withCredentials 添加标头,如下所示:

return this.http.get<Settings>(`/api/settings`,  withCredentials: true );

【讨论】:

以上是关于显然是随机错误:“防伪令牌验证失败。防伪 cookie 令牌和请求令牌不匹配。”的主要内容,如果未能解决你的问题,请参考以下文章

显然是随机矢量绘图:TSNE

通过 Appirater 获得的 App Store ID 与来自 plist 的 ID 不同,而且显然是随机的

复习:聊聊hive随机采样①

Codeforces 1114E(数学+随机算法)

ABRecordCopyValue 可以返回啥? (解决错误的访问)

一个错误的答案显然意味着一个逻辑错误,但啥是错误的想法呢?