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

登录页面闲置20-30分钟后触发AntiForgeryToken HTTP 400错误

解决方案:解决ASP.NET Core登录页面AntiforgeryToken闲置超时400错误

你的问题核心在于AntiforgeryToken默认绑定到用户会话,ASP.NET Core的匿名会话默认超时时间(通常20-30分钟)到期后,服务器端存储的令牌数据会被清除,导致页面中旧令牌验证失败触发400错误。延长TokenLifespan无效是因为会话先于令牌过期,而非令牌本身到期。

以下是几种可行的解决方案:

方案1:配置Antiforgery使用分布式缓存,脱离会话依赖

这是最彻底的解决方式,让令牌存储在分布式缓存(而非会话)中,同时设置足够长的有效期,适配登录页面可能闲置数日的场景。

步骤1:添加分布式缓存服务

根据环境选择合适的缓存实现,开发环境可用内存缓存,生产环境推荐Redis:

// 开发环境(Program.cs/Startup.cs)
services.AddDistributedMemoryCache();

// 生产环境(Redis示例)
services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-redis-connection-string";
    options.InstanceName = "Antiforgery-";
});

步骤2:重新配置Antiforgery

修改Antiforgery配置,禁用会话绑定,设置令牌和Cookie的长有效期:

services.AddAntiforgery(options =>
{
    options.HeaderName = "XSRF-TOKEN";
    // 配置Cookie属性,允许前端读取,设置长有效期
    options.Cookie.Name = "XSRF-TOKEN";
    options.Cookie.HttpOnly = false;
    options.Cookie.Expiration = TimeSpan.FromDays(7);
    options.Cookie.SameSite = SameSiteMode.Strict;
    // 设置令牌本身的有效期
    options.TokenLifespan = TimeSpan.FromDays(7);
});

注:ASP.NET Core 3.0+版本中,当注册了IDistributedCache服务后,Antiforgery会自动使用它存储令牌数据,不再依赖会话。

方案2:自定义Antiforgery验证逻辑(针对登录接口)

如果不想修改全局缓存配置,可以针对登录接口编写自定义验证过滤器,允许匿名用户的旧令牌在一定范围内有效,或者在验证失败时自动刷新令牌并重试。

实现自定义过滤器

public class LoginAntiforgeryFilter : IAsyncAuthorizationFilter
{
    private readonly IAntiforgery _antiforgery;
    private readonly ILogger<LoginAntiforgeryFilter> _logger;

    public LoginAntiforgeryFilter(IAntiforgery antiforgery, ILogger<LoginAntiforgeryFilter> logger)
    {
        _antiforgery = antiforgery;
        _logger = logger;
    }

    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        if (context.HttpContext.Request.Method != HttpMethods.Post)
        {
            return;
        }

        try
        {
            await _antiforgery.ValidateRequestAsync(context.HttpContext);
        }
        catch (AntiforgeryValidationException ex)
        {
            _logger.LogWarning(ex, "Antiforgery token validation failed for login request");
            
            // 针对登录接口,返回新令牌让前端重试
            if (context.ActionDescriptor.RouteValues["controller"] == "Account" && 
                context.ActionDescriptor.RouteValues["action"] == "Login")
            {
                var tokens = _antiforgery.GetAndStoreTokens(context.HttpContext);
                context.Result = new JsonResult(new 
                { 
                    Success = false, 
                    NeedRefreshToken = true,
                    NewToken = tokens.RequestToken 
                });
            }
            else
            {
                context.Result = new BadRequestResult();
            }
        }
    }
}

注册并替换默认过滤器

在Program.cs/Startup.cs中注册自定义过滤器,并替换默认的AutoValidateAntiforgeryTokenAttribute:

services.AddScoped<LoginAntiforgeryFilter>();

services.AddControllersWithViews(options =>
{
    // 移除默认的Antiforgery过滤器
    var defaultFilter = options.Filters.FirstOrDefault(f => f is AutoValidateAntiforgeryTokenAttribute);
    if (defaultFilter != null)
    {
        options.Filters.Remove(defaultFilter);
    }
    // 添加自定义过滤器
    options.Filters.Add<LoginAntiforgeryFilter>();
});

前端适配重试逻辑

修改Ajax请求,收到NeedRefreshToken时更新令牌并重试:

function submitLogin() {
    var token = $('input[name=__RequestVerificationToken]').val();
    $.ajax({
        url: '/Account/Login',
        type: 'POST',
        beforeSend: function (xhr) { 
            xhr.setRequestHeader('XSRF-TOKEN', token); 
        },
        data: $('#loginForm').serialize(),
        success: function (res) {
            if (res.NeedRefreshToken) {
                // 更新页面中的令牌
                $('input[name=__RequestVerificationToken]').val(res.NewToken);
                // 重试提交
                submitLogin();
            } else if (res.Success) {
                // 登录成功逻辑
                window.location.href = '/Home/Index';
            } else {
                // 显示错误信息
                alert(res.Message);
            }
        },
        error: function (xhr) {
            if (xhr.status === 400) {
                // 兜底处理,刷新页面获取新令牌
                window.location.reload();
            }
        }
    });
}

方案3:延长匿名会话超时时间

如果你的应用允许,可以延长匿名会话的超时时间,让会话和令牌同步过期:

services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromDays(7);
    options.Cookie.HttpOnly = true;
    options.Cookie.SameSite = SameSiteMode.Strict;
});

注:这种方式会增加服务器内存消耗(如果用内存会话存储),生产环境建议配合分布式会话存储使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:41:01