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

ASP.NET Core5应用AuthenticationTicket过期后无法接收请求如何解决

你的站点无响应的核心原因是自定义ITicketStore实现中使用了同步Redis调用,且异步方法未正确实现异步逻辑,大量请求触发时导致ASP.NET Core线程池饥饿,无法处理新请求。你遇到的"所有请求超时、控制台无日志"表现刚好符合线程池耗尽的特征:请求还没进入ASP.NET Core处理管道就因为没有可用线程被阻塞,因此不会产生任何日志输出。


具体问题点

  1. Redis操作全部为同步执行:cache.Set、cache.Get<byte[]>、cache.Remove都是同步方法,会直接阻塞调用线程。在Ticket过期后,所有携带过期Cookie的请求都会触发Redis查询,大量同步阻塞瞬间耗尽线程池可用工作线程。
  2. 异步方法未正确实现:RenewAsync、RetrieveAsync、RemoveAsync都没有实际的异步操作,直接返回Task.CompletedTask,相当于把异步方法当成同步方法用,进一步加剧线程阻塞。
  3. 过期时间不匹配:Cookie的过期时间设为2分钟,但Redis中存储Ticket的TTL硬编码为5分钟,会出现Cookie未过期但Redis中Ticket已被清理的异常情况。

解决方案

第一步:给RedisCacheService补充异步方法

首先扩展你的RedisCacheService,提供原生异步操作接口:

// RedisCacheService新增以下异步方法
public async Task SetAsync(string key, byte[] value, TimeSpan ttl)
{
    await _database.StringSetAsync(key, value, ttl);
}

public async Task<byte[]> GetAsync(string key)
{
    return await _database.StringGetAsync(key);
}

public async Task RemoveAsync(string key)
{
    await _database.KeyDeleteAsync(key);
}

第二步:重写RedisCacheTicketStore为全异步实现

public class RedisCacheTicketStore : ITicketStore
{
    private readonly RedisCacheService cache;
    private readonly IConfiguration configuration;
    private readonly ILogger logger;

    public RedisCacheTicketStore(
        RedisCacheService redisCacheService, 
        IConfiguration configuration, 
        ILogger logger)
    {
        this.cache = redisCacheService;
        this.configuration = configuration;
        this.logger = logger;
    }

    public async Task<string> StoreAsync(AuthenticationTicket ticket)
    {
        var key = $"AuthSessionStore-{Guid.NewGuid()}";
        await RenewAsync(key, ticket);
        logger.Debug("The ticket {Key} was stored.", key);
        return key;
    }

    public async Task RenewAsync(string key, AuthenticationTicket ticket)
    {
        // 直接从Ticket属性取过期时间,和Cookie逻辑保持一致
        var timeToLive = ticket.Properties.ExpiresUtc.HasValue 
            ? ticket.Properties.ExpiresUtc.Value - DateTimeOffset.UtcNow
            : TimeSpan.FromMinutes(configuration.GetValue("SessionCookieLifetimeMinutes", 60));
        
        var bytes = SerializeToBytes(ticket);
        await cache.SetAsync(key, bytes, timeToLive);
        logger.Debug("The ticket was renew and will be expire at {ExpiresAtUtc}", ticket.Properties.ExpiresUtc);
    }

    public async Task<AuthenticationTicket> RetrieveAsync(string key)
    {
        var bytes = await cache.GetAsync(key);
        var ticket = DeserializeFromBytes(bytes);
        logger.Debug("The ticket {Key} was retrieved.", key);
        return ticket;
    }

    public async Task RemoveAsync(string key)
    {
        await cache.RemoveAsync(key);
        logger.Debug("The ticket {Key} was removed.", key);
    }

    private static byte[] SerializeToBytes(AuthenticationTicket source)
    {
        return TicketSerializer.Default.Serialize(source);
    }

    private static AuthenticationTicket DeserializeFromBytes(byte[] source)
    {
        return source == null ? null : TicketSerializer.Default.Deserialize(source);
    }
}

修改完成后,当Ticket过期时RetrieveAsync会返回null,ASP.NET Core Cookie认证中间件会自动将请求标记为未认证,触发挑战流程跳转到登录页,符合你的预期。


分布式缓存场景Cookie会话最佳实践

  • 所有缓存操作必须使用异步实现,绝对不能在异步管道中使用同步IO阻塞线程
  • Ticket的过期时间直接从AuthenticationTicket.Properties.ExpiresUtc获取,和Cookie本身的过期逻辑保持一致,避免两边过期时间不匹配
  • 增加Redis故障容错机制:可以给Redis操作加超时和异常捕获,出现异常时直接返回null,让中间件走重新登录逻辑,避免Redis故障拖垮整个站点
  • 如果开启滑动过期,需要每次调用RetrieveAsync时判断剩余TTL,不足一定比例时自动调用RenewAsync刷新过期时间

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 17:57:05