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
相关产品推荐
相关产品推荐

