.NET Core 3.1 部署到共享主机后认证Cookie短时间内失效问题
问题原因
- 数据保护密钥未持久化配置:ASP.NET Core默认使用数据保护API加密认证Cookie,默认情况下密钥存储在进程内存中,共享托管环境下应用池会定期回收、或存在多实例负载均衡的情况,每次回收/切换实例都会生成新密钥,导致之前签发的Cookie无法被解密,直接判定为无效,用户被登出。这是最符合30-40秒失效场景的诱因,很多共享托管的应用池闲置回收时间设置得非常短,刚好吻合该失效周期。
- 分布式缓存配置错误:你配置了
AddDistributedMemoryCache,这是进程内的内存缓存,仅适合单实例开发环境使用,共享托管多实例/应用池回收后缓存内容全部丢失,若Cookie认证的会话相关校验依赖该缓存就会失效。 - 中间件顺序错误:
Configure方法中的中间件执行顺序不符合规范,你将UseHttpsRedirection、UseStaticFiles放在了身份认证和授权中间件之后,会导致请求在还没完成HTTPS跳转时就进入认证逻辑,可能出现Cookie跨协议不匹配、认证上下文丢失的问题。 - 异步方法未等待:
SignUserIn方法中调用异步方法HttpContext.SignInAsync时没有使用await关键字,也未将方法标记为async Task<IActionResult>,异步操作未完成就提前返回重定向,可能导致Cookie签发不完全,出现偶发的认证失效。
解决方案
- 配置数据保护密钥持久化:在
ConfigureServices方法中添加数据保护配置,将密钥存储到共享托管允许读写的本地目录,确保应用池回收、实例切换后密钥不会丢失,示例代码:
services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"你的站点可读写目录路径\DataProtection-Keys")) .SetApplicationName("你的应用名称");
注意要确保配置的目录站点运行身份有读写权限。
- 替换分布式缓存实现:如果是共享托管单实例场景,可以直接移除
AddDistributedMemoryCache的配置,或者如果后续要扩展多实例,改用共享的分布式缓存实现比如Redis、SqlServer分布式缓存。 - 修正中间件顺序:将
Configure方法中的中间件调整为正确的执行顺序,修改后代码如下:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } // 调整顺序:先HTTPS重定向 app.UseHttpsRedirection(); // 再处理静态文件 app.UseStaticFiles(); // 再路由 app.UseRouting(); // 再Session app.UseSession(); // 再认证、授权 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
- 修复异步方法调用:将
SignUserIn和SignUserOut方法改为异步实现,等待异步方法执行完成:
private async Task<IActionResult> SignUserIn(LoginViewModel model, UserDTO user) { // 原有生成Claims等逻辑不变 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, _claimsPrincipal, _authProperties); // 原有重定向逻辑不变 } private async Task<IActionResult> SignUserOut() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); HttpContext.Session.Clear(); return RedirectToAction("Index"); } // 对应的Login和SignOut方法也要改为异步: public async Task<IActionResult> Login(LoginViewModel model) { // 原有逻辑不变 if (_user != null && _user.Id > 0) { return await SignUserIn(model, _user); } // 原有逻辑不变 } public async Task<IActionResult> SignOut() { return await SignUserOut(); }
内容的提问来源于stack exchange,提问作者Kevin Dark
相关产品推荐
相关产品推荐

