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

C#实现应用关闭后持久登录状态的技术问询

实现持久化登录(类似Steam免登功能)

问题核心

当前登录仅在应用会话内有效,重启后需重新登录;XML存储账户信息安全性不足,需实现「非主动登出则保持登录」的持久化功能。

解决方案步骤

1. 修复SQL注入漏洞(优先处理)

你的现有代码存在严重SQL注入风险,必须改用参数化查询:

private void btnLogin_Click(object sender, EventArgs e)
{
    string loginQuery = "SELECT UserID FROM Account WHERE UserID = @UserID AND Password = @Password";
    using (SqlCommand cmd = new SqlCommand(loginQuery, SqlCon))
    {
        cmd.Parameters.AddWithValue("@UserID", txtusername.Text.Trim());
        cmd.Parameters.AddWithValue("@Password", txtpassword.Text.Trim());
        // 注意:生产环境中密码需存储哈希值(如BCrypt),此处仅示例

        SqlDataAdapter sda = new SqlDataAdapter(cmd);
        DataTable dt = new DataTable();
        sda.Fill(dt);

        if (dt.Rows.Count == 1)
        {
            // 登录成功,触发持久化逻辑
            SetupPersistentLogin(dt.Rows[0]["UserID"].ToString());
            // 执行登录后的页面跳转等操作
        }
        else
        {
            MessageBox.Show("无效的用户名或密码。");
        }
    }
}

2. 安全存储持久化凭证

不直接存储用户ID或密码,改用唯一登录令牌机制:

  • 在Account表新增RefreshToken(字符串类型)和TokenExpiry(日期类型)字段
  • 登录成功时生成随机长字符串作为令牌,设置过期时间(如30天),同步更新到数据库
  • 用系统加密API将令牌加密后存储在本地(仅当前Windows用户可解密)

本地令牌存储示例

using System.Security.Cryptography;
using System.IO;

private void SetupPersistentLogin(string userId)
{
    // 生成随机令牌
    string refreshToken = Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N");
    DateTime tokenExpiry = DateTime.Now.AddDays(30);

    // 更新数据库令牌信息
    UpdateDbRefreshToken(userId, refreshToken, tokenExpiry);

    // 加密后存储到应用私有目录
    byte[] encryptedToken = ProtectedData.Protect(
        Encoding.UTF8.GetBytes(refreshToken),
        null,
        DataProtectionScope.CurrentUser);
    string tokenDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "YourAppName");
    Directory.CreateDirectory(tokenDir);
    File.WriteAllBytes(Path.Combine(tokenDir, "login_token.dat"), encryptedToken);
}

private void UpdateDbRefreshToken(string userId, string refreshToken, DateTime expiry)
{
    string updateQuery = "UPDATE Account SET RefreshToken = @RefreshToken, TokenExpiry = @TokenExpiry WHERE UserID = @UserID";
    using (SqlCommand cmd = new SqlCommand(updateQuery, SqlCon))
    {
        cmd.Parameters.AddWithValue("@UserID", userId);
        cmd.Parameters.AddWithValue("@RefreshToken", refreshToken);
        cmd.Parameters.AddWithValue("@TokenExpiry", expiry);
        SqlCon.Open();
        cmd.ExecuteNonQuery();
        SqlCon.Close();
    }
}

3. 应用启动时自动验证登录

在主窗体加载事件中,读取本地令牌并验证有效性:

private void MainForm_Load(object sender, EventArgs e)
{
    string tokenPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "YourAppName", "login_token.dat");
    if (File.Exists(tokenPath))
    {
        try
        {
            byte[] encryptedToken = File.ReadAllBytes(tokenPath);
            byte[] decryptedToken = ProtectedData.Unprotect(
                encryptedToken,
                null,
                DataProtectionScope.CurrentUser);
            string refreshToken = Encoding.UTF8.GetString(decryptedToken);

            // 验证令牌是否有效且未过期
            if (IsValidRefreshToken(refreshToken))
            {
                // 自动登录成功,初始化用户状态
                AutoLogin(refreshToken);
                return;
            }
        }
        catch
        {
            // 解密失败或令牌无效,删除本地文件
            File.Delete(tokenPath);
        }
    }
    // 验证失败,显示登录界面
    new LoginForm().ShowDialog();
}

private bool IsValidRefreshToken(string refreshToken)
{
    string query = "SELECT COUNT(*) FROM Account WHERE RefreshToken = @RefreshToken AND TokenExpiry > GETDATE()";
    using (SqlCommand cmd = new SqlCommand(query, SqlCon))
    {
        cmd.Parameters.AddWithValue("@RefreshToken", refreshToken);
        SqlCon.Open();
        int count = (int)cmd.ExecuteScalar();
        SqlCon.Close();
        return count == 1;
    }
}

private void AutoLogin(string refreshToken)
{
    string query = "SELECT UserID FROM Account WHERE RefreshToken = @RefreshToken";
    using (SqlCommand cmd = new SqlCommand(query, SqlCon))
    {
        cmd.Parameters.AddWithValue("@RefreshToken", refreshToken);
        SqlDataAdapter sda = new SqlDataAdapter(cmd);
        DataTable dt = new DataTable();
        sda.Fill(dt);
        if (dt.Rows.Count == 1)
        {
            // 执行登录后的初始化操作,如加载用户数据
        }
    }
}

4. 主动登出逻辑

用户点击登出时,清除本地令牌并重置数据库中的令牌记录:

private void btnLogout_Click(object sender, EventArgs e)
{
    string currentUserId = GetCurrentLoggedInUserId(); // 需实现获取当前用户ID的逻辑

    // 重置数据库令牌
    string updateQuery = "UPDATE Account SET RefreshToken = NULL, TokenExpiry = NULL WHERE UserID = @UserID";
    using (SqlCommand cmd = new SqlCommand(updateQuery, SqlCon))
    {
        cmd.Parameters.AddWithValue("@UserID", currentUserId);
        SqlCon.Open();
        cmd.ExecuteNonQuery();
        SqlCon.Close();
    }

    // 删除本地令牌文件
    string tokenPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "YourAppName", "login_token.dat");
    if (File.Exists(tokenPath))
    {
        File.Delete(tokenPath);
    }

    // 跳转登录界面
    this.Hide();
    new LoginForm().ShowDialog();
    this.Close();
}

安全提示

  • 数据库中禁止存储明文密码,应存储BCrypt等哈希算法生成的密码哈希值
  • 自动登录成功后建议轮换新的RefreshToken,降低令牌泄露风险
  • 令牌过期时间不宜过长,建议设置为7-30天

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 16:20:47