显然是随机错误:“防伪令牌验证失败。防伪 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 令牌和请求令牌不匹配。”的主要内容,如果未能解决你的问题,请参考以下文章
通过 Appirater 获得的 App Store ID 与来自 plist 的 ID 不同,而且显然是随机的