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

.NET 8多标签页下ASP.NET Core防伪令牌失效及替代方案咨询

在.NET 8中解决多标签页场景下的Antiforgery令牌无效问题

当ASP.NET Core使用默认的同步器模式时,多标签页场景下新获取的令牌会覆盖旧令牌,导致之前标签页提交请求时验证失败。以下是三种可行的替代模式及具体实现:

这种模式不需要维护服务器端的令牌同步,令牌基于用户Cookie中的标识生成,每个标签页的令牌都能独立验证,不会互相影响。

配置步骤

在Program.cs中配置Antiforgery服务:

builder.Services.AddAntiforgery(options =>
{
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.HeaderName = "X-CSRF-TOKEN";
    // 启用基于Cookie的令牌生成,无需服务器端存储同步
    options.UseCookieTokenProvider();
});

前端使用示例

MVC视图中生成隐藏字段:

@inject IAntiforgery Antiforgery
@{
    var tokenSet = Antiforgery.GetAndStoreTokens(HttpContext);
}
<form method="post" action="/Home/Submit">
    <input type="hidden" name="__RequestVerificationToken" value="@tokenSet.RequestToken" />
    <!-- 表单内容 -->
    <button type="submit">提交</button>
</form>

AJAX请求中设置请求头:

fetch('/api/values', {
    method: 'POST',
    headers: {
        'X-CSRF-TOKEN': '@tokenSet.RequestToken',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: 'test' })
});

2. 自定义分布式缓存令牌存储模式

通过实现自定义的IAntiforgeryTokenStore,将令牌与用户ID关联存储在分布式缓存中,允许同一用户拥有多个有效令牌,避免新标签页覆盖旧令牌。

实现自定义令牌存储

public class CustomAntiforgeryTokenStore : IAntiforgeryTokenStore
{
    private readonly IDistributedCache _distributedCache;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public CustomAntiforgeryTokenStore(IDistributedCache distributedCache, IHttpContextAccessor httpContextAccessor)
    {
        _distributedCache = distributedCache;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<AntiforgeryToken> GetTokenAsync(HttpContext httpContext, string tokenName)
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId)) return null;

        var cacheKey = $"AntiforgeryTokens:{userId}:{tokenName}";
        var tokenJson = await _distributedCache.GetStringAsync(cacheKey);
        return tokenJson != null ? JsonSerializer.Deserialize<AntiforgeryToken>(tokenJson) : null;
    }

    public async Task SaveTokenAsync(HttpContext httpContext, string tokenName, AntiforgeryToken token)
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId)) return;

        var cacheKey = $"AntiforgeryTokens:{userId}:{tokenName}";
        var tokenJson = JsonSerializer.Serialize(token);
        await _distributedCache.SetStringAsync(cacheKey, tokenJson, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2)
        });
    }

    public async Task RemoveTokenAsync(HttpContext httpContext, string tokenName)
    {
        var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId)) return;

        var cacheKey = $"AntiforgeryTokens:{userId}:{tokenName}";
        await _distributedCache.RemoveAsync(cacheKey);
    }
}

注册服务

在Program.cs中添加分布式缓存和自定义存储的注册:

builder.Services.AddHttpContextAccessor();
// 这里以Redis为例,可替换为SQL Server缓存或其他分布式缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-redis-connection-string";
});
builder.Services.AddScoped<IAntiforgeryTokenStore, CustomAntiforgeryTokenStore>();
builder.Services.AddAntiforgery(options =>
{
    options.Cookie.Name = ".AspNetCore.Antiforgery.Custom";
    options.HeaderName = "X-CSRF-TOKEN";
});

3. 双重Cookie验证模式

参考OWASP推荐的CSRF防护方案,使用两个Cookie:一个HttpOnly的Cookie存储用户会话标识,另一个可访问的Cookie存储CSRF令牌。验证时校验两者的关联关系,同一用户的多个标签页可共用会话标识,令牌不会互相覆盖。

配置步骤

builder.Services.AddAntiforgery(options =>
{
    // 主Cookie:HttpOnly,存储用户会话上下文
    options.Cookie.Name = ".AspNetCore.Antiforgery.Session";
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    // 令牌Cookie:非HttpOnly,供前端读取
    options.FormFieldName = "__RequestVerificationToken";
    options.HeaderName = "X-CSRF-TOKEN";
    // 设置令牌有效期,避免长期有效
    options.TokenLifespan = TimeSpan.FromHours(4);
});

前端获取令牌示例

function getCsrfToken() {
    const cookiePairs = document.cookie.split(';').map(c => c.trim().split('='));
    const tokenCookie = cookiePairs.find(pair => pair[0] === '__RequestVerificationToken');
    return tokenCookie ? tokenCookie[1] : null;
}

// 提交AJAX请求
fetch('/Home/Submit', {
    method: 'POST',
    headers: {
        'X-CSRF-TOKEN': getCsrfToken(),
        'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: new URLSearchParams({ data: 'example' })
});

模式选择建议

  • 无状态API应用:优先选择Cookie-Based无状态模式,配置简单且无需服务器存储;
  • 需要严格令牌生命周期管理:选用自定义分布式缓存模式,可精准控制每个令牌的有效期和销毁逻辑;
  • 传统MVC/Razor Pages应用:双重Cookie模式兼容性最好,无需大幅修改现有代码。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 07:53:17