用户登出后如何使AspNetCore.Identity.Application Cookie失效
问题根因
ASP.NET Core Identity 默认使用无状态的Cookie认证机制:加密后的完整认证票据直接存储在客户端Cookie中,服务端默认不会留存已生效票据的校验记录。只要Cookie本身未超出有效期、签名验证通过,服务端就会判定为合法请求,因此用户登出后即使删除了本地Cookie,提前复制的旧有效Cookie仍然可以正常通过身份校验。
解决方案
方案1:接入服务端票据存储(最彻底,推荐)
将完整认证票据存储在服务端(支持内存、Redis、数据库等存储介质),客户端Cookie仅留存票据唯一标识,登出时直接删除服务端对应的票据,即可让旧Cookie完全失效。
实现步骤如下:
- 实现自定义
ITicketStore,以下是基于分布式缓存的实现示例:
public class DistributedCacheTicketStore : ITicketStore { private readonly IDistributedCache _cache; private const string KeyPrefix = "AuthTicket_"; public DistributedCacheTicketStore(IDistributedCache cache) { _cache = cache; } public async Task<string> StoreAsync(AuthenticationTicket ticket) { var key = KeyPrefix + Guid.NewGuid().ToString("N"); await SaveTicketToCache(key, ticket); return key; } public async Task RenewAsync(string key, AuthenticationTicket ticket) { await SaveTicketToCache(key, ticket); } public async Task<AuthenticationTicket> RetrieveAsync(string key) { var ticketBytes = await _cache.GetAsync(key); return ticketBytes == null ? null : TicketSerializer.Default.Deserialize(ticketBytes); } public async Task RemoveAsync(string key) { await _cache.RemoveAsync(key); } private async Task SaveTicketToCache(string key, AuthenticationTicket ticket) { var cacheOptions = new DistributedCacheEntryOptions(); if (ticket.Properties.ExpiresUtc.HasValue) { cacheOptions.AbsoluteExpiration = ticket.Properties.ExpiresUtc.Value; } await _cache.SetAsync(key, TicketSerializer.Default.Serialize(ticket), cacheOptions); } }
- 在Program.cs中注册服务,替换默认的客户端票据存储逻辑:
// 注册分布式缓存,测试可先用内存缓存,生产环境建议替换为Redis builder.Services.AddDistributedMemoryCache(); // 注册自定义票据存储 builder.Services.AddScoped<ITicketStore, DistributedCacheTicketStore>(); // 配置Identity Cookie使用服务端票据存储 builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; }) .AddIdentityCookies(options => { options.ApplicationCookie.Configure(cookieOpt => { cookieOpt.SessionStore = builder.Services.BuildServiceProvider().GetRequiredService<ITicketStore>(); }); });
- 原有登出逻辑无需修改,
SignOutAsync方法会自动调用ITicketStore.RemoveAsync删除服务端对应的票据,旧Cookie即使被复用也无法在服务端找到匹配的票据,直接会被判定为未登录。
方案2:添加Cookie动态校验逻辑(轻量实现)
通过配置Cookie的OnValidatePrincipal验证事件,每次请求校验Cookie的有效性,比如维护已登出票据的黑名单,校验时直接拒绝黑名单内的Cookie:
builder.Services.ConfigureApplicationCookie(opt => { opt.Events = new CookieAuthenticationEvents { OnValidatePrincipal = async context => { var userId = context.Principal?.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrEmpty(userId)) { context.RejectPrincipal(); return; } // 登录时可给AuthenticationTicket添加唯一票据ID,存在Properties中 var ticketId = context.Properties.Items.TryGetValue("TicketId", out var id) ? id : string.Empty; if (string.IsNullOrEmpty(ticketId)) return; var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>(); // 登出时将对应TicketId存入黑名单缓存,缓存有效期和Cookie有效期一致即可 var isInvalid = await cache.GetStringAsync($"InvalidTicket:{ticketId}"); if (!string.IsNullOrEmpty(isInvalid)) { context.RejectPrincipal(); await context.HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); } } }; });
辅助优化
可以配合缩短Cookie有效期,降低旧Cookie的可用时间窗口:
builder.Services.ConfigureApplicationCookie(opt => { opt.ExpireTimeSpan = TimeSpan.FromHours(2); // 配置Cookie绝对有效期为2小时 opt.SlidingExpiration = true; // 开启滑动过期,活跃用户操作时自动续期 });
内容的提问来源于stack exchange,提问作者Niranga
相关产品推荐
相关产品推荐

