You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Asp.Net Core中@Html.AntiForgeryToken()旧令牌失效问题解决咨询

实现ASP.NET Core防伪令牌多令牌有效(会话期内)

ASP.NET Core默认的防伪令牌机制会在每次生成新令牌时更新Cookie中的主令牌,验证时仅认可最新的令牌,这就是导致旧标签页失效的核心原因。要实现和ASP.NET Framework一致的多令牌有效逻辑,需要自定义防伪令牌存储,让系统在会话中保留所有生成过的有效令牌,验证时逐一匹配。

步骤1:自定义基于Session的防伪令牌存储

创建实现IAntiforgeryTokenStore接口的存储类,将生成的令牌存入Session集合而非覆盖单一值,同时自动清理过期令牌:

using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Linq;

public class SessionBasedAntiforgeryTokenStore : IAntiforgeryTokenStore
{
    private const string ValidTokensSessionKey = "Antiforgery_ValidTokens";
    private readonly IHttpContextAccessor _httpContextAccessor;

    public SessionBasedAntiforgeryTokenStore(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public AntiforgeryToken GetCookieToken()
    {
        // 保留默认逻辑:从Cookie读取主令牌
        var context = _httpContextAccessor.HttpContext;
        return context.Request.Cookies.TryGetValue(AntiforgeryDefaults.CookieName, out var cookieVal)
            ? AntiforgeryTokenSerializer.Deserialize(cookieVal)
            : null;
    }

    public void SaveCookieToken(AntiforgeryToken token)
    {
        var context = _httpContextAccessor.HttpContext;
        // 将主令牌写入Cookie
        var serializedToken = AntiforgeryTokenSerializer.Serialize(token);
        context.Response.Cookies.Append(AntiforgeryDefaults.CookieName, serializedToken, new CookieOptions
        {
            HttpOnly = true,
            Secure = context.Request.IsHttps,
            SameSite = SameSiteMode.Lax,
            Expires = token.ExpiresUtc
        });

        // 更新Session中的有效令牌列表(自动清理过期项)
        var validTokens = GetCurrentValidTokens();
        validTokens.RemoveAll(t => t.ExpiresUtc < DateTime.UtcNow);
        if (!validTokens.Any(t => t.Hash.Equals(token.Hash)))
        {
            validTokens.Add(token);
            context.Session.Set(ValidTokensSessionKey, SerializeTokens(validTokens));
        }
    }

    public AntiforgeryToken GetRequestToken(HttpContext httpContext)
    {
        // 从表单或请求头获取提交的令牌
        var requestToken = httpContext.Request.Form[AntiforgeryDefaults.FormFieldName].FirstOrDefault();
        if (string.IsNullOrEmpty(requestToken))
        {
            requestToken = httpContext.Request.Headers[AntiforgeryDefaults.HeaderName].FirstOrDefault();
        }
        return string.IsNullOrEmpty(requestToken) ? null : AntiforgeryTokenSerializer.Deserialize(requestToken);
    }

    // 内部方法:获取Session中当前有效的令牌列表
    internal List<AntiforgeryToken> GetCurrentValidTokens()
    {
        var context = _httpContextAccessor.HttpContext;
        if (context.Session.TryGetValue(ValidTokensSessionKey, out var tokenData))
        {
            return DeserializeTokens(tokenData);
        }
        return new List<AntiforgeryToken>();
    }

    // 序列化/反序列化令牌列表(示例用JSON,实际可换更高效的二进制序列化)
    private byte[] SerializeTokens(List<AntiforgeryToken> tokens)
    {
        var json = System.Text.Json.JsonSerializer.Serialize(tokens);
        return System.Text.Encoding.UTF8.GetBytes(json);
    }

    private List<AntiforgeryToken> DeserializeTokens(byte[] data)
    {
        var json = System.Text.Encoding.UTF8.GetString(data);
        return System.Text.Json.JsonSerializer.Deserialize<List<AntiforgeryToken>>(json) ?? new List<AntiforgeryToken>();
    }
}

步骤2:替换默认服务并配置Session

在Program.cs中注册自定义存储,替换默认的防伪令牌实现,同时确保Session服务已启用:

var builder = WebApplication.CreateBuilder(args);

// 配置防伪令牌基础参数
builder.Services.AddAntiforgery(options =>
{
    options.Cookie.Name = ".AspNetCore.Antiforgery";
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
});

// 替换默认令牌存储为自定义Session实现
builder.Services.AddScoped<IAntiforgeryTokenStore, SessionBasedAntiforgeryTokenStore>();

// 配置Session(按需调整超时时间)
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30);
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});

builder.Services.AddControllersWithViews();

var app = builder.Build();

// 启用Session(必须在UseRouting之前)
app.UseSession();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

步骤3:自定义验证逻辑适配多令牌

默认验证仅检查最新Cookie令牌,需自定义过滤器让验证逻辑匹配Session中所有有效令牌:

public class MultiTokenAntiforgeryFilter : IAsyncAuthorizationFilter
{
    private readonly IAntiforgery _antiforgery;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public MultiTokenAntiforgeryFilter(IAntiforgery antiforgery, IHttpContextAccessor httpContextAccessor)
    {
        _antiforgery = antiforgery;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        var httpContext = context.HttpContext;
        var tokens = _antiforgery.GetAndStoreTokens(httpContext);
        var submittedToken = AntiforgeryTokenSerializer.Deserialize(tokens.RequestToken);

        // 获取Session中所有有效令牌
        var tokenStore = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<SessionBasedAntiforgeryTokenStore>();
        var validTokens = tokenStore.GetCurrentValidTokens();
        
        // 检查提交的令牌是否存在于有效列表中且未过期
        var isValid = validTokens.Any(t => 
            t.Hash.Equals(submittedToken.Hash) && 
            t.ExpiresUtc > DateTime.UtcNow);

        if (!isValid)
        {
            context.Result = new ForbidResult();
            return;
        }

        // 执行默认验证的其他逻辑(如用户标识匹配)
        await _antiforgery.ValidateRequestAsync(httpContext);
    }
}

然后注册过滤器(全局或单个控制器):

// 全局注册(替换默认的AutoValidateAntiforgeryTokenAttribute)
builder.Services.AddMvc(options =>
{
    options.Filters.Add<MultiTokenAntiforgeryFilter>();
});

// 或单个控制器注册
[TypeFilter(typeof(MultiTokenAntiforgeryFilter))]
public class HomeController : Controller
{
    // ...
}

注意事项

  • Session依赖:确保app.UseSession()已在管道中正确配置,且Session Cookie具备安全属性。
  • 令牌清理:每次添加新令牌时自动清理过期项,避免Session存储冗余。
  • 安全性:防伪Cookie和Session Cookie都需配置HttpOnly、Secure、SameSite,防止XSS和CSRF风险。

内容的提问来源于stack exchange,提问作者Eric

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 01:03:12