AntiforgeryValidationException: 提供的防伪令牌是针对不同基于声明的用户而非当前用户的。

8
我不确定这里发生了什么,但我在我的cookie中看到了2个不同的令牌。一个是“XSRF-TOKEN”,另一个是“.AspNetCore.Antiforgery.OnvOIX6Mzn8”,它们有不同的值。
我正在使用ASP.Net Core 2.1,并设置了SPA(前端使用Angular),在Startup.cs中添加了以下内容。
我不知道是什么创建了后面的那个令牌,因为它似乎不是我添加的任何代码生成的。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IAntiforgery antiforgery)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseExceptionHandler(
            builder =>
            {
            builder.Run(
                async context =>
                {
                context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
                context.Response.Headers.Add("Access-Control-Allow-Origin", "*");

                var error = context.Features.Get<IExceptionHandlerFeature>();
                if (error != null)
                {
                    context.Response.AddApplicationError(error.Error.Message);
                    await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false);
                }
                });
            });
    }

    app.UseAuthentication();
    app.UseHttpsRedirection();
    app.UseDefaultFiles();
    app.UseStaticFiles();
    app.UseJwtTokenMiddleware();
    app.UseSpaStaticFiles();
    app.UseCookiePolicy();

    app.UseMvc(routes =>
    {
            routes.MapRoute(
                name: "default",
                template: "{controller}/{action=Index}/{id?}");
        });

        app.UseSpa(spa =>
        {
            spa.Options.SourcePath = "ClientApp";
            spa.UseSpaPrerendering(options =>
            {
                options.BootModulePath = $"{spa.Options.SourcePath}/dist/server/main.js";
                options.BootModuleBuilder = env.IsDevelopment()
                    ? new AngularCliBuilder(npmScript: "build:ssr")
                    : null;
                options.ExcludeUrls = new[] { "/sockjs-node" };
            });

            if (env.IsDevelopment())
            {
                spa.UseAngularCliServer(npmScript: "start");
            }
        });

        app.UseMiddleware<AntiForgeryMiddleware>("XSRF-TOKEN");
    }
}

public static class ApplicationBuilderExtensions
{
    public static IApplicationBuilder UseAntiforgeryTokenMiddleware(this IApplicationBuilder builder, string requestTokenCookieName)
    {
        return builder.UseMiddleware<AntiForgeryMiddleware>(requestTokenCookieName);
    }
}

public class AntiForgeryMiddleware
{
    private readonly RequestDelegate next;
    private readonly string requestTokenCookieName;
    private readonly string[] httpVerbs = new string[] { "GET", "HEAD", "OPTIONS", "TRACE" };

public AntiForgeryMiddleware(RequestDelegate next, string requestTokenCookieName)
{
    this.next = next;
    this.requestTokenCookieName = requestTokenCookieName;
}

public async Task Invoke(HttpContext context, IAntiforgery antiforgery)
{
    if (httpVerbs.Contains(context.Request.Method, StringComparer.OrdinalIgnoreCase))
    {
        var tokens = antiforgery.GetAndStoreTokens(context);

        context.Response.Cookies.Append(requestTokenCookieName, tokens.RequestToken, new CookieOptions()
        {
            HttpOnly = false
        });
    }

    await next.Invoke(context);
}

}


我没有使用表单标签助手。我在最初的帖子中没有提到它,但我已经更新了它,说明它有一个Angular SPA,并且是预渲染的。我不使用Razor。 - Jonas Arcangel
2个回答

4
在您的示例中,.AspNetCore.Antiforgery.OnvOIX6Mzn8 cookie是由调用GetAndStoreTokens生成的。这个调用生成了两个令牌:
  1. “Cookie Token”:这是写为.AspNetCore.Antiforgery.OnvOIX6Mzn8的内容。
  2. “Request Token”:这是与“Cookie Token”配对的单独令牌。您需要像您的代码中所示那样将此值作为cookie自己写出(即XSRF-TOKEN)。
如果您检查生成的值tokens,您会看到这一点。它有两个感兴趣的属性:RequestTokenCookieTokenGetAndStoreTokens调用正在写出CookieToken值,而您的代码正在写出RequestToken值,这就解释了为什么您在两个不同的cookie中看到了两个不同的值。
您给出的代码似乎直接来自文档,其中解释了:

...使用应用程序主页的中间件生成防伪令牌,并将其作为cookie(使用稍后在本主题中描述的默认Angular命名约定)发送到响应中...

当向服务器发送需要根据防伪规则进行验证的请求时,需要同时提供cookie和相应的标头。ASP.NET Core防伪系统在验证过程中匹配“请求令牌”和“Cookie令牌”。
文档中还有更多内容:

...使用本地存储在客户端上存储防伪令牌并将令牌作为请求标头发送是一种推荐方法。

随后的JavaScript示例显示了如何使用自定义请求标头(即RequestVerificationToken)向XHR请求添加RequestToken值。文档还显示,在ASP.NET Core DI系统中注册Antiforgery服务时可以更改此标头。
services.AddAntiforgery(options => options.HeaderName = "X-CSRF-TOKEN");

您需要在Angular应用程序中读取XSRF-TOKEN cookie的值,并将其与API请求一起发送回服务器。您可以使用默认标头名称(如上所述),也可以自定义它(也如上所述)。正如@Sal在他的答案中指出的那样,如果cookie名称为XSRF-TOKEN,则AngularJs具有内置机制可实现此功能(这对于Angular也是一样的)。
希望这解释了两个令牌和一般防伪过程。然而,从您为Angular的默认HttpClient XSRF保护正确命名的cookie写出来的事实来看,我期望所有这些方面都已正确配置。
就您问题可能存在的位置而言,我怀疑这行代码的位置:
app.UseMiddleware<AntiForgeryMiddleware>("XSRF-TOKEN");

考虑到这是您的管道设置代码中的最后一次调用,它只会在MVC管道之后运行,并且仅在MVC管道无法处理请求的情况下运行。为了解决这个问题,请将调用移到UseMvc之上 - 这可能有点激进,因为它会生成比必要更多的“请求令牌”,但它将确认是否是您的问题。


我试图将它放到其他地方(包括在 UseMvc 上面),但我仍然遇到相同的问题。 - Jonas Arcangel

1
默认情况下,AspNetCore使用“AspNetCore.AntiForgery.XXX”创建一个cookie(除非被您的配置重命名),以防止xsrf/csrf攻击。
我还在MSDN文章中读到,AngularJS也有处理xsrf/csrf场景的自动方式:
AngularJS使用一种惯例来解决CSRF。如果服务器发送一个名为XSRF-TOKEN的cookie,AngularJS $http服务会在向服务器发送请求时将cookie值添加到标头中。这个过程是自动的。标头不需要明确设置。标头名称是X-XSRF-TOKEN。服务器应该检测到这个标头并验证它的内容。 对于ASP.NET Core API工作,请按照以下惯例进行设置: - 配置您的应用程序以在名为XSRF-TOKEN的cookie中提供令牌。 - 配置防伪服务以查找名为X-XSRF-TOKEN的标头。
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");

你的应用程序是否配置为同时生成默认的 AntiForgery cookie 和 XSRF-TOKEN?如果是这种情况,你可能会生成和接收到两个不同的防伪标记,这可能会导致问题。


我看不到其他AspNetCore.Antiforgery.XXX被生成的位置。如果我删除我的自定义代码(“app.UseMiddleware<AntiForgeryMiddleware>(“XSRF-TOKEN”);”),则两个令牌都不会生成。 - Jonas Arcangel
你可以尝试移除你的自定义代码,并在 Startup.ConfigureService 中定制 token,类似于以下代码:services.AddAntiforgery(options => { options.CookieName = "XSRF-TOKEN"; options.HeaderName = "XSRF-TOKEN"; }); - Sal
我没有收到任何令牌。AddAntiforgery代码没有显式地发送令牌。(请注意,我正在使用SpaPrerendering。) - Jonas Arcangel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接