.NET Forms Authentication无超时配置及跨浏览器会话持久化咨询
针对你的AJAX单页应用场景,要实现Forms Authentication无超时且仅通过注销退出,同时解决跨浏览器会话持久化问题,我给你整理了实用的方案:
因为你用的是AJAX单页应用,不需要依赖meta刷新,我们可以通过主动刷新认证票证的方式避免超时,同时规避设置过高timeout的潜在风险:
1. 基础配置调整
先在web.config里配置Forms Auth,设置一个较长的基础过期时间,同时关闭滑动过期(我们自己手动刷新):
<authentication mode="Forms"> <forms loginUrl="~/Account/Login.aspx" name=".ASPXAUTH" path="/" requireSSL="false" timeout="525600" <!-- 1年,换算为分钟 --> slidingExpiration="false" cookieless="UseCookies" enableCrossAppRedirects="false" /> </authentication>
2. 全局AJAX请求刷新票证
创建一个自定义Action过滤器,在每次AJAX请求处理前,检查用户是否已登录,若已登录则重新生成认证票证并更新Cookie的过期时间——这样用户每次操作(比如编辑GridView的AJAX请求)都会延长会话有效期,相当于“永不过期”:
public class RefreshAuthTicketFilter : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var user = filterContext.HttpContext.User; if (user.Identity.IsAuthenticated && user.Identity is FormsIdentity formsIdentity) { var oldTicket = formsIdentity.Ticket; // 生成新票证,保持过期时间为当前时间+1年 var newTicket = new FormsAuthenticationTicket( oldTicket.Version, oldTicket.Name, DateTime.Now, DateTime.Now.AddYears(1), oldTicket.IsPersistent, oldTicket.UserData, oldTicket.CookiePath); // 加密并更新Cookie var encryptedTicket = FormsAuthentication.Encrypt(newTicket); var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket) { Expires = newTicket.Expiration, Path = FormsAuthentication.FormsCookiePath, HttpOnly = true, Secure = FormsAuthentication.RequireSSL }; filterContext.HttpContext.Response.Cookies.Set(authCookie); } base.OnActionExecuting(filterContext); } }
将这个过滤器应用到所有处理AJAX请求的控制器或Action上即可。
3. 注销逻辑实现
仅通过注销按钮退出的核心是清除认证Cookie,在注销接口中执行以下操作:
public ActionResult Logout() { FormsAuthentication.SignOut(); // 强制清除Cookie var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName) { Expires = DateTime.Now.AddDays(-1), Path = FormsAuthentication.FormsCookiePath }; Response.Cookies.Set(authCookie); return Json(new { success = true }); }
前端通过AJAX调用该接口后,即可完成退出操作。
浏览器的Cookie是隔离的,要实现跨浏览器自动登录,需要结合服务器端令牌存储和SqlMembershipProvider来管理用户身份:
1. 配置SqlMembershipProvider
首先在web.config中配置连接字符串和会员提供者,用于统一管理用户账号信息:
<connectionStrings> <add name="SqlServices" connectionString="Data Source=.;Initial Catalog=AspNetDB;Integrated Security=True" /> </connectionStrings> <system.web> <membership defaultProvider="SqlProvider"> <providers> <clear /> <add name="SqlProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="SqlServices" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" /> </providers> </membership> </system.web>
可以通过aspnet_regsql.exe工具快速生成对应的数据库表。
2. 跨浏览器登录逻辑实现
核心思路是:用户登录时生成唯一令牌,存储到服务器数据库,同时在浏览器设置持久化Cookie;用户用其他浏览器访问时,通过Cookie中的令牌验证身份并自动登录。
登录时的令牌生成与存储
public ActionResult Login(string username, string password, bool rememberMe) { if (Membership.ValidateUser(username, password)) { var user = Membership.GetUser(username); if (rememberMe) { // 生成唯一令牌 var token = Guid.NewGuid().ToString(); var expiration = DateTime.Now.AddYears(1); // 存储令牌到自定义表(需提前创建UserPersistentTokens表) using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlServices"].ConnectionString)) { conn.Open(); var cmd = new SqlCommand("INSERT INTO UserPersistentTokens (UserId, Token, Expiration) VALUES (@UserId, @Token, @Expiration)", conn); cmd.Parameters.AddWithValue("@UserId", user.ProviderUserKey); cmd.Parameters.AddWithValue("@Token", token); cmd.Parameters.AddWithValue("@Expiration", expiration); cmd.ExecuteNonQuery(); } // 设置持久化令牌Cookie var tokenCookie = new HttpCookie("PersistentAuthToken", token) { Expires = expiration, Path = "/", HttpOnly = true, Secure = FormsAuthentication.RequireSSL }; Response.Cookies.Set(tokenCookie); } // 生成Forms Auth票证 FormsAuthentication.SetAuthCookie(username, rememberMe); return Json(new { success = true }); } return Json(new { success = false, message = "用户名或密码错误" }); }
全局自动登录验证
在Global.asax的Application_BeginRequest中,检查未登录用户是否携带有效令牌,若有效则自动登录:
protected void Application_BeginRequest(object sender, EventArgs e) { var context = HttpContext.Current; if (!context.User.Identity.IsAuthenticated) { var tokenCookie = context.Request.Cookies["PersistentAuthToken"]; if (tokenCookie != null && !string.IsNullOrEmpty(tokenCookie.Value)) { var token = tokenCookie.Value; using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlServices"].ConnectionString)) { conn.Open(); var cmd = new SqlCommand("SELECT UserId, Expiration FROM UserPersistentTokens WHERE Token = @Token AND Expiration > @Now", conn); cmd.Parameters.AddWithValue("@Token", token); cmd.Parameters.AddWithValue("@Now", DateTime.Now); var reader = cmd.ExecuteReader(); if (reader.Read()) { var userId = reader["UserId"]; var user = Membership.GetUser(userId); if (user != null) { // 自动登录并更新令牌有效期 FormsAuthentication.SetAuthCookie(user.UserName, true); var updateCmd = new SqlCommand("UPDATE UserPersistentTokens SET Expiration = @NewExpiration WHERE Token = @Token", conn); updateCmd.Parameters.AddWithValue("@NewExpiration", DateTime.Now.AddYears(1)); updateCmd.Parameters.AddWithValue("@Token", token); updateCmd.ExecuteNonQuery(); } } } } } }
注销时清理令牌
注销时需要同时清除Cookie和服务器端的令牌记录:
public ActionResult Logout() { FormsAuthentication.SignOut(); // 清理服务器端令牌 var tokenCookie = Request.Cookies["PersistentAuthToken"]; if (tokenCookie != null && !string.IsNullOrEmpty(tokenCookie.Value)) { using (var conn = new SqlConnection(ConfigurationManager.ConnectionStrings["SqlServices"].ConnectionString)) { conn.Open(); var cmd = new SqlCommand("DELETE FROM UserPersistentTokens WHERE Token = @Token", conn); cmd.Parameters.AddWithValue("@Token", tokenCookie.Value); cmd.ExecuteNonQuery(); } // 清除令牌Cookie tokenCookie.Expires = DateTime.Now.AddDays(-1); Response.Cookies.Set(tokenCookie); } // 清除Forms Auth Cookie var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName) { Expires = DateTime.Now.AddDays(-1), Path = FormsAuthentication.FormsCookiePath }; Response.Cookies.Set(authCookie); return Json(new { success = true }); }
- 安全性:务必开启
HttpOnly和Secure(HTTPS环境下)Cookie属性,防止XSS攻击;建议将令牌存储为哈希值而非明文,降低数据库泄露后的风险。 - SqlMembershipProvider:它是.NET框架自带的成熟用户管理组件,支持注册、密码重置、角色管理等功能,若需要自定义用户字段,可结合ProfileProvider或自定义表关联其
UserId。
内容的提问来源于stack exchange,提问作者iamdlm




