.NET Core应用重启后用户保持登录的原因及解决方法
问题原因
ASP.NET Core 基于Cookie的认证机制核心逻辑是:用户登录成功后,服务端会将身份声明(Claims)加密后写入客户端浏览器的Cookie中,后续请求只要携带未过期、可被服务端正常解密的认证Cookie,就会直接判定用户为已登录状态,不需要依赖服务端存储的会话数据。
应用重启后登录状态保留的核心原因有两个:
- 认证Cookie被设置为持久化模式:登录逻辑中设置了
IsPersistent = true,Cookie会被持久化存储到用户本地磁盘,不会随浏览器关闭、应用重启自动删除,且配置的Cookie有效期为60分钟,有效期内浏览器每次请求都会自动携带该Cookie。 - 服务端未配置重启后的凭证失效校验:默认配置下,ASP.NET Core的数据保护密钥会持久化存储在操作系统/容器的固定目录下(IIS Express环境存在当前用户的APPDATA目录,Docker环境如果是重启未销毁容器,密钥会保留在容器文件系统中),应用重启后依然可以用原有密钥解密客户端传来的旧认证Cookie,没有额外的校验逻辑拦截旧实例签发的凭证。如果是销毁容器后重新创建实例,默认配置下数据保护密钥会重置,旧Cookie自然解密失败,但如果是重启未销毁容器、或者显式配置了持久化密钥的场景,旧Cookie依然有效。
导致问题的代码段
一共有两处相关配置导致该现象:
Startup.cs中Cookie认证的配置段,未添加应用实例标识校验、未配置随应用重置的加密密钥:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Home/Index"; options.AccessDeniedPath = "/Error/403"; // 此处缺少重启失效相关配置 });
HomeController.cs登录逻辑中,显式开启了持久化Cookie:
AuthenticationProperties authProperties = new AuthenticationProperties { AllowRefresh = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60), IsPersistent = true, // 该行设置Cookie持久化存储到本地 IssuedUtc = DateTimeOffset.UtcNow };
另外当前代码的中间件顺序存在错误,app.UseSession()需要放在app.UseAuthentication()和app.UseAuthorization()之前,否则认证流程无法正常读取Session数据,虽然不是本次问题的诱因,但需要修正。
修复方案
要实现应用重启/重新部署后所有已登录用户凭证自动失效,按以下步骤修改即可:
- 在
Startup类中添加一个静态变量,存储当前应用实例的唯一启动标识,应用每次重启该值都会重新生成:
public class Startup { // 应用启动时自动生成唯一ID,重启后重置 private static readonly string _appInstanceStamp = Guid.NewGuid().ToString("N"); public Startup(IConfiguration configuration) { Configuration = configuration; } // 其余原有代码保持不变 }
- 修改
ConfigureServices中的Cookie认证配置,添加票据签发和校验逻辑:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Home/Index"; options.AccessDeniedPath = "/Error/403"; options.Events = new CookieAuthenticationEvents { // 用户登录签发Cookie时,把当前实例标识写入票据 OnSigningIn = context => { context.Properties.Items["AppInstanceStamp"] = _appInstanceStamp; return Task.CompletedTask; }, // 每次请求校验Cookie时,对比实例标识 OnValidatePrincipal = async context => { // 票据中没有标识、或者标识和当前实例不匹配,判定凭证失效 if (!context.Properties.Items.TryGetValue("AppInstanceStamp", out var stamp) || stamp != _appInstanceStamp) { context.RejectPrincipal(); await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); } } }; });
- (可选)如果不需要浏览器关闭后依然保持登录状态,可以把登录逻辑中的
IsPersistent = true改为IsPersistent = false,此时认证Cookie为会话级Cookie,浏览器关闭后就会自动删除。 - 修正中间件顺序,把
UseSession移到UseAuthentication之前:
app.UseRouting(); // 调整Session中间件位置 app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); // 其余原有中间件保持不变
修改完成后,每次应用重启/重新部署,_appInstanceStamp都会生成新的随机值,所有旧实例签发的认证Cookie都会因为标识不匹配被直接拒绝,强制用户跳转到登录页重新登录,该方案不依赖数据保护密钥的存储逻辑,可以覆盖所有重启/重部署场景。
内容的提问来源于stack exchange,提问作者Aleks Vujic
相关产品推荐
相关产品推荐

