You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET C#中如何创建安全的唯一用户会话ID

会话安全改造:用不可猜测的会话ID替代账号ID

当前问题

现有会话逻辑直接将account-id存入Cookie,账号ID易被猜测,存在身份伪造风险。需要替换为不可猜测的唯一值(如GUID),同时实现服务器端的会话关联校验。

当前代码实现

创建会话代码

public static void NewSession(Account account)
{
    var redirectUrl = "Login.aspx";
    if (account == null)
    {
        var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
        if (sessionCookie != null)
            ExpireCookie(sessionCookie);
    }
    else
    {
        var sessionCookie = new HttpCookie("test-app-session");
        sessionCookie.Values["account-id"] = account.Id.ToString();
        sessionCookie.Expires = DateTime.Now.AddHours(12);
        HttpContext.Current.Response.Cookies.Add(sessionCookie);

        var redirectCookie = HttpContext.Current.Request.Cookies["test-app-redirect"];
        if (redirectCookie != null)
        {
            redirectUrl = redirectCookie.Values["url"];
            ExpireCookie(redirectCookie);
        }
        if (string.IsNullOrWhiteSpace(redirectUrl))
            redirectUrl = "Default.aspx";
    }
    HttpContext.Current.Response.Redirect(redirectUrl);
}

验证会话代码

public static Account FromSession()
{
    var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
    if (sessionCookie != null && long.TryParse(sessionCookie.Values["account-id"], out long accountId))
    {
        using (var db = Database.Connect())
        {
            using (var cmd = db.Command("SELECT * FROM Account WHERE id=@id").Parameter("@id", accountId, DbType.Int64))
            using (var reader = cmd.ExecuteReader())
                if (reader.Read())
                    return new Account(reader);
        }
    }
    if (!Path.GetFileName(HttpContext.Current.Request.Path).Equals("Login.aspx", StringComparison.OrdinalIgnoreCase))
    {
        var redirectCookie = new HttpCookie("test-app-redirect");
        redirectCookie.Values["url"] = HttpContext.Current.Request.Url.ToString();
        redirectCookie.Expires = DateTime.Now.AddHours(1);
        HttpContext.Current.Response.Cookies.Add(redirectCookie);
        HttpContext.Current.Response.Redirect("Login.aspx");
    }
    return null;
}

改造方案

核心思路是在服务器端维护会话与账号的映射关系:生成不可猜测的会话ID存入Cookie,同时在数据库中保存会话ID对应的账号信息、过期时间等校验数据。

1. 新增会话存储表

在数据库中创建Session表,用于存储会话关联信息:

CREATE TABLE Session (
    SessionId VARCHAR(36) PRIMARY KEY, -- GUID字符串
    AccountId BIGINT NOT NULL,
    ExpiresAt DATETIME NOT NULL,
    CreatedAt DATETIME DEFAULT GETDATE(),
    -- 可选:绑定客户端IP增强安全
    ClientIp VARCHAR(45),
    FOREIGN KEY (AccountId) REFERENCES Account(id)
)

2. 修改会话创建方法NewSession

生成GUID作为会话ID,存入Cookie,同时将会话信息写入数据库:

public static void NewSession(Account account)
{
    var redirectUrl = "Login.aspx";
    if (account == null)
    {
        var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
        if (sessionCookie != null)
        {
            // 删除服务器端会话记录
            using (var db = Database.Connect())
            {
                var sessionId = sessionCookie.Values["session-id"];
                if (!string.IsNullOrWhiteSpace(sessionId))
                {
                    db.Command("DELETE FROM Session WHERE SessionId=@sessionId")
                      .Parameter("@sessionId", sessionId)
                      .ExecuteNonQuery();
                }
                ExpireCookie(sessionCookie);
            }
        }
    }
    else
    {
        var sessionId = Guid.NewGuid().ToString();
        var expiresAt = DateTime.Now.AddHours(12);
        
        // 写入数据库会话记录
        using (var db = Database.Connect())
        {
            var clientIp = HttpContext.Current.Request.UserHostAddress;
            db.Command("INSERT INTO Session (SessionId, AccountId, ExpiresAt, ClientIp) VALUES (@sessionId, @accountId, @expiresAt, @clientIp)")
              .Parameter("@sessionId", sessionId)
              .Parameter("@accountId", account.Id)
              .Parameter("@expiresAt", expiresAt)
              .Parameter("@clientIp", clientIp)
              .ExecuteNonQuery();
        }
        
        // 创建会话Cookie
        var sessionCookie = new HttpCookie("test-app-session");
        sessionCookie.Values["session-id"] = sessionId;
        sessionCookie.Expires = expiresAt;
        // 安全优化:开启HttpOnly防止XSS窃取Cookie,开启Secure仅在HTTPS下传输
        sessionCookie.HttpOnly = true;
        sessionCookie.Secure = HttpContext.Current.Request.IsSecureConnection;
        HttpContext.Current.Response.Cookies.Add(sessionCookie);

        var redirectCookie = HttpContext.Current.Request.Cookies["test-app-redirect"];
        if (redirectCookie != null)
        {
            redirectUrl = redirectCookie.Values["url"];
            ExpireCookie(redirectCookie);
        }
        if (string.IsNullOrWhiteSpace(redirectUrl))
            redirectUrl = "Default.aspx";
    }
    HttpContext.Current.Response.Redirect(redirectUrl);
}

3. 修改会话验证方法FromSession

从Cookie获取会话ID,查询数据库获取关联账号,同时校验会话是否过期:

public static Account FromSession()
{
    var sessionCookie = HttpContext.Current.Request.Cookies["test-app-session"];
    if (sessionCookie != null)
    {
        var sessionId = sessionCookie.Values["session-id"];
        if (!string.IsNullOrWhiteSpace(sessionId))
        {
            using (var db = Database.Connect())
            {
                // 查询会话并校验过期时间,可选校验IP
                var clientIp = HttpContext.Current.Request.UserHostAddress;
                using (var cmd = db.Command(@"
                    SELECT a.* 
                    FROM Account a
                    JOIN Session s ON a.id = s.AccountId
                    WHERE s.SessionId=@sessionId 
                      AND s.ExpiresAt > GETDATE()
                      AND s.ClientIp = @clientIp")
                    .Parameter("@sessionId", sessionId)
                    .Parameter("@clientIp", clientIp))
                using (var reader = cmd.ExecuteReader())
                {
                    if (reader.Read())
                    {
                        // 可选:延长会话过期时间
                        db.Command("UPDATE Session SET ExpiresAt=@newExpires WHERE SessionId=@sessionId")
                          .Parameter("@newExpires", DateTime.Now.AddHours(12))
                          .Parameter("@sessionId", sessionId)
                          .ExecuteNonQuery();
                          
                        return new Account(reader);
                    }
                    else
                    {
                        // 会话过期或无效,删除记录并清除Cookie
                        db.Command("DELETE FROM Session WHERE SessionId=@sessionId")
                          .Parameter("@sessionId", sessionId)
                          .ExecuteNonQuery();
                        ExpireCookie(sessionCookie);
                    }
                }
            }
        }
    }
    
    if (!Path.GetFileName(HttpContext.Current.Request.Path).Equals("Login.aspx", StringComparison.OrdinalIgnoreCase))
    {
        var redirectCookie = new HttpCookie("test-app-redirect");
        redirectCookie.Values["url"] = HttpContext.Current.Request.Url.ToString();
        redirectCookie.Expires = DateTime.Now.AddHours(1);
        HttpContext.Current.Response.Cookies.Add(redirectCookie);
        HttpContext.Current.Response.Redirect("Login.aspx");
    }
    return null;
}

4. 额外安全建议

  • 定期清理数据库中过期的会话记录(可通过定时任务或SQL触发器实现)
  • 开启Cookie的SameSite属性(如SameSite=Strict或Lax)防止CSRF攻击
  • 避免在Cookie中存储任何敏感信息,所有校验逻辑都在服务器端完成

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 02:57:33