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
相关产品推荐
相关产品推荐

