ASP.NET MVC表单认证滑动过期后旧Auth Cookie仍有效问题咨询
这个坑我之前踩过!本质是ASP.NET的表单认证默认是无状态的加密票据机制——只要票据没到自身的过期时间,不管是不是旧的,服务器都会认。滑动过期只是生成了一张新的、延长了有效期的票据,但旧票据在它自己的过期窗口内依然能正常通过验证,这就给会话劫持留了口子。
下面给几个实用的解决方案,你可以根据业务场景选:
1. 绑定客户端唯一标识(最推荐)
把Auth Cookie和客户端的专属信息绑定,比如User-Agent+IP的哈希值,每次验证时核对这些信息是否匹配。这样就算旧Cookie被盗,换个浏览器/IP就用不了。
代码示例:
首先,在生成认证票据时,把客户端标识存入UserData:
// 生成客户端唯一标识的方法 private string GetClientIdentifier() { string userAgent = Request.UserAgent ?? ""; string ipAddress = Request.UserHostAddress; // 哈希处理避免明文存储 return HashHelper.ComputeSha256Hash(userAgent + ipAddress); } // 登录时生成票据 var authTicket = new FormsAuthenticationTicket( version: 1, name: username, issueDate: DateTime.Now, expiration: DateTime.Now.AddMinutes(30), isPersistent: rememberMe, userData: GetClientIdentifier(), // 存入客户端标识 cookiePath: FormsAuthentication.FormsCookiePath ); string encryptedTicket = FormsAuthentication.Encrypt(authTicket); var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, Expires = authTicket.Expiration }; Response.Cookies.Add(authCookie);
然后在Global.asax的Application_AuthenticateRequest里验证:
protected void Application_AuthenticateRequest(object sender, EventArgs e) { var context = HttpContext.Current; if (context.User?.Identity is FormsIdentity formsIdentity) { var ticket = formsIdentity.Ticket; string storedClientId = ticket.UserData; string currentClientId = GetClientIdentifier(); if (!string.Equals(storedClientId, currentClientId, StringComparison.Ordinal)) { // 客户端不匹配,强制注销 FormsAuthentication.SignOut(); context.Response.Redirect("~/Account/Login"); } } }
2. 关闭滑动过期,改用短周期绝对过期
如果业务对用户体验要求不高,可以关闭滑动过期,设置较短的绝对过期时间(比如15分钟),让旧Cookie快速失效。缺点是用户需要频繁重新登录。
在web.config里配置:
<authentication mode="Forms"> <forms loginUrl="~/Account/Login" timeout="15" slidingExpiration="false" /> <!-- 关闭滑动过期 --> </authentication>
3. 服务器端跟踪已失效票据
维护一个服务器端的“废弃票据列表”(用缓存或者数据库),每次生成新票据时,把旧票据的标识加入列表,验证时先检查票据是否在废弃列表里。
代码示例:
生成新票据时记录旧票据:
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null) { string oldCookieValue = Request.Cookies[FormsAuthentication.FormsCookieName].Value; try { var oldTicket = FormsAuthentication.Decrypt(oldCookieValue); // 缓存旧票据,有效期设为旧票据的剩余时间 var cacheKey = $"InvalidatedTicket_{oldTicket.Name}_{oldTicket.IssueDate.Ticks}"; Context.Cache.Add( cacheKey, true, dependencies: null, absoluteExpiration: oldTicket.Expiration, slidingExpiration: Cache.NoSlidingExpiration, priority: CacheItemPriority.Normal, onRemoveCallback: null ); } catch (Exception) { // 解密失败忽略,可能是无效Cookie } }
验证时检查列表:
protected void Application_AuthenticateRequest(object sender, EventArgs e) { var context = HttpContext.Current; if (context.User?.Identity is FormsIdentity formsIdentity) { var ticket = formsIdentity.Ticket; var cacheKey = $"InvalidatedTicket_{ticket.Name}_{ticket.IssueDate.Ticks}"; if (context.Cache[cacheKey] != null) { // 旧票据已被废弃 FormsAuthentication.SignOut(); context.Response.Redirect("~/Account/Login"); } } }
注意:这种方法会增加服务器开销,缓存里会存大量过期票据,要确保缓存能自动清理过期条目。
总结一下:默认的表单认证为了无状态性牺牲了部分安全性,要解决旧Cookie复用问题,要么绑定客户端标识,要么放弃无状态跟踪旧票据,要么缩短过期时间。
内容的提问来源于stack exchange,提问作者Pablo Santa Cruz
相关产品推荐
相关产品推荐

