ASP.NET Core中Cookie/Session提前过期问题求助
问题现象
在ASP.NET Core项目的Program.cs中已将Cookie和Session的过期时间设置为7天,但系统仍会在30分钟后自动登出。Chrome中查看Cookie过期时间显示为2025年(符合预期),多次调整配置代码无效,怀疑自定义CookieMiddleware是诱因。
项目配置代码
builder.Services.AddAuthentication(option => { option.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; option.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }).AddCookie(option => { option.LoginPath = new PathString("/home/login"); option.ExpireTimeSpan = TimeSpan.FromDays(7); option.AccessDeniedPath = new PathString("/"); option.SlidingExpiration = false; option.Cookie.HttpOnly = true; option.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; option.Cookie.Name = "UserAuthCookie"; }).AddGoogle("GOOGLE_LOGIN", option => { option.ClientId = "0000000000000-0000000000000.apps.googleusercontent.com"; option.ClientSecret = "0000000000-0000000000000"; option.CallbackPath = "/home/GoogleLoginCallback/"; option.Events = new OAuthEvents { OnRemoteFailure = context => { context.Response.Redirect("/Home/login"); context.HandleResponse(); return Task.CompletedTask; } }; }).AddGoogle("GOOGLE_SIGNUP", option => { option.ClientId = "00000000-000000000.apps.googleusercontent.com"; option.ClientSecret = "00000000000-0000000000"; option.CallbackPath = "/home/GoogleSignupCallback/"; option.Events = new OAuthEvents { OnRemoteFailure = context => { context.Response.Redirect("/Home/signup"); context.HandleResponse(); return Task.CompletedTask; } }; }); builder.Services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder.WithOrigins("https://filmnamenevisan.ir") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); // allow cookies }); builder.Services.ConfigureApplicationCookie(options => { options.Cookie.Name = "UserAuthCookie"; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = false; }); builder.Services.AddAuthorization(option => { option.AddPolicy(RolesConstants.King, policy => policy.RequireRole(RolesConstants.King)); option.AddPolicy(RolesConstants.Admin, policy => policy.RequireRole(RolesConstants.Admin)); option.AddPolicy(RolesConstants.Expert, policy => policy.RequireRole(RolesConstants.Expert)); option.AddPolicy(RolesConstants.User, policy => policy.RequireRole(RolesConstants.User)); }); builder.Services.AddSession(options => { options.Cookie.Name = "UserSession"; options.IdleTimeout = TimeSpan.FromDays(7); options.Cookie.HttpOnly = true; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; }); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCors("CorsPolicy"); app.UseCookiePolicy(); app.UseSession(); app.UseAuthentication(); app.UseRouting(); app.UseAuthorization(); app.UseMiddleware<CookieMiddleware>();
自定义CookieMiddleware代码
public class CookieMiddleware { private readonly RequestDelegate _next; private readonly IServiceProvider _serviceProvider; public CookieMiddleware(RequestDelegate next, IServiceProvider serviceProvider) { _next = next; _serviceProvider = serviceProvider; } public async Task Invoke(HttpContext context) { if (context.User.Identity.IsAuthenticated) { if (!context.Session.Keys.Contains("UserSession") || !context.Session.TryGetValue("UserSession:user-id", out _)) { context.Session.SetString("UserSession:user-id", ClaimUtility.GetUserId(context.User as ClaimsPrincipal).ToString()); context.Session.SetString("UserSession:user-nickname", ClaimUtility.GetUserFullname(context.User as ClaimsPrincipal)); using (var scope = _serviceProvider.CreateScope()) { var dbContext = scope.ServiceProvider.GetRequiredService<IUserFacade>(); var retrieve = dbContext.GetUserUsernameHeadshotByIdService.Execute(new RequestGetUserUsernameHeadshotByIdServiceDto { UserId = Guid.Parse(context.Session.GetString("UserSession:user-id")), }); if (retrieve != null) { if (retrieve.Username != null) context.Session.SetString("UserSession:user-username", retrieve.Username); if (string.IsNullOrEmpty(retrieve.Headshot)) context.Session.SetString("UserSession:user-privateheadshot", GeneralConstants.PublicHeadshot); else context.Session.SetString("UserSession:user-privateheadshot", $"/UploadedStuff/user/images/user-headshot/{retrieve.Headshot}-thumb.jpg"); } else { context.Session.SetString("UserSession:user-privateheadshot", GeneralConstants.PublicHeadshot); } } } } await _next(context); } }
排查解决方法
1. 修正中间件执行顺序
当前自定义中间件CookieMiddleware注册在UseAuthorization()之后,此时用户身份信息可能还未完成认证初始化,导致Session逻辑异常。调整顺序,将其移到UseAuthentication()之后、UseAuthorization()之前:
app.UseAuthentication(); app.UseMiddleware<CookieMiddleware>(); // 调整到此处 app.UseRouting(); app.UseAuthorization();
ASP.NET Core中间件按注册顺序执行,认证完成后才能正确获取context.User的身份信息,确保自定义中间件能正确处理Session数据。
2. 修复Session键判断逻辑
中间件中!context.Session.Keys.Contains("UserSession")的判断永远为true,因为Session的Keys存储的是具体键名(如UserSession:user-id),不存在UserSession这个键。修改判断逻辑:
// 原代码 // if (!context.Session.Keys.Contains("UserSession") || !context.Session.TryGetValue("UserSession:user-id", out _)) // 修改为 if (!context.Session.TryGetValue("UserSession:user-id", out _))
避免每次请求都重复初始化Session,减少不必要的Session操作。
3. 确认登录时的Ticket过期时间
检查登录逻辑,确保调用SignInAsync时指定持久化属性和过期时间,覆盖默认30分钟设置:
await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity), new AuthenticationProperties { ExpiresUtc = DateTimeOffset.UtcNow.AddDays(7), IsPersistent = true });
即使在AddCookie中配置了ExpireTimeSpan,部分场景下(如第三方登录回调)仍需显式指定过期属性。
4. 统一Cookie安全策略配置
AddCookie和ConfigureApplicationCookie中CookieSecurePolicy配置不一致,统一设置为CookieSecurePolicy.Always,确保HTTPS环境下Cookie正常传递:
// 在AddCookie中修改 option.Cookie.SecurePolicy = CookieSecurePolicy.Always;
避免配置冲突导致浏览器拒绝Cookie。
5. 检查Session存储方式(生产环境)
默认Session使用内存存储,应用池回收(默认30分钟)会导致Session丢失。生产环境建议使用分布式缓存(如Redis)持久化Session:
// 示例:添加Redis缓存 builder.Services.AddStackExchangeRedisCache(options => { options.Configuration = "your-redis-connection-string"; options.InstanceName = "SampleInstance"; });
确保Session数据不会因应用池回收丢失。
内容的提问来源于stack exchange,提问作者Ali.M Eghbaldar

