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

问题描述 投票:0回答:5

背景

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

对于导致上述 stdout 输出的请求,

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 数据? 有人有这方面的经验或有什么建议吗? 谢谢你。

c# iis asp.net-core csrf kestrel-http-server
5个回答
4
投票

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

services.AddMvc().AddRazorPagesOptions(options =>
{
    options.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute());
});

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


0
投票

我在这里找到了解决方案: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>();

0
投票

关于 CookieToken 为空:也许该请求是伪造的?由于 cookie 丢失,我怀疑您的网站确实发送了每个请求。什么时候才能缺失呢?当它来自其他地方时。

关于另一个问题:

  1. 是您的中间位置,在Configure方法的身份验证部分之后
    将 AntiforgeryToken 作为 cookie 添加到响应中吗?
  2. 您是否有可能在多个 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 });
    

0
投票
我正在寻找新的 .NET 6.0 Blazor 服务器应用程序的解决方案,具有身份验证和修复功能,如 MSDN,无需禁用防伪功能:

app.UseRouting(); app.UseAuthorization(); var antiforgery = app.Services.GetRequiredService<IAntiforgery>(); app.Use((context, next) => { var requestPath = context.Request.Path.Value; if (string.Equals(requestPath, "/", StringComparison.OrdinalIgnoreCase) || string.Equals(requestPath, "/index.html", StringComparison.OrdinalIgnoreCase)) { var tokenSet = antiforgery.GetAndStoreTokens(context); context.Response.Cookies.Append("XSRF-TOKEN", tokenSet.RequestToken!, new CookieOptions { HttpOnly = false }); } return next(context); });
    

0
投票
对我来说,问题是我只是忘记发送标头

X-XSRF-TOKEN

为了验证工作,我们需要 cookie 和标头。

© www.soinside.com 2019 - 2024. All rights reserved.