You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

.NET Forms Authentication无超时配置及跨浏览器会话持久化咨询

针对你的AJAX单页应用场景,要实现Forms Authentication无超时且仅通过注销退出,同时解决跨浏览器会话持久化问题,我给你整理了实用的方案:

一、实现无超时的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.asaxApplication_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 });
}
注意事项
  • 安全性:务必开启HttpOnlySecure(HTTPS环境下)Cookie属性,防止XSS攻击;建议将令牌存储为哈希值而非明文,降低数据库泄露后的风险。
  • SqlMembershipProvider:它是.NET框架自带的成熟用户管理组件,支持注册、密码重置、角色管理等功能,若需要自定义用户字段,可结合ProfileProvider或自定义表关联其UserId

内容的提问来源于stack exchange,提问作者iamdlm

火山引擎 最新活动