Files
lserp_cs_6.0/其他程序/Lskj.GetSecretKey/LoginFrm.cs
T
2026-07-02 10:11:39 +00:00

454 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Lskj.Control;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Windows.Forms;
namespace Lskj.GetSecretKey
{
public partial class LoginFrm : BaseForm
{
private ContextMenuStrip _ctx;
public LoginFrm()
{
InitializeComponent();
SecureUserStore.EnsureInitialized(); // 初始化本地存储(含默认账号)
InitUi();
WireEvents();
}
#region UI & 事件
private void InitUi()
{
// 密码框设置为 * 掩码(兼容 TextBox / DevExpress TextEdit
SetPasswordMask(passWordEidt);
// 回填上次成功登录账号
var last = SecureUserStore.GetLastUser();
if (!string.IsNullOrEmpty(last)) { userNameEdit.Text = last; passWordEidt.Focus(); }
else userNameEdit.Focus();
// 提示
try
{
setPanel.Cursor = Cursors.Hand;
var tip = new ToolTip();
tip.SetToolTip(setPanel, "左键:修改密码(需当前密码验证)\r\n右键:更多操作(重置密码…)");
}
catch { /* ignore */ }
// 右键菜单
_ctx = new ContextMenuStrip();
var miChange = new ToolStripMenuItem("修改密码…", null, (s, e) => DoChangePassword());
var miReset = new ToolStripMenuItem("重置密码…", null, (s, e) => DoResetPassword());
_ctx.Items.AddRange(new ToolStripItem[] { miChange, new ToolStripSeparator(), miReset });
setPanel.ContextMenuStrip = _ctx;
}
private void WireEvents()
{
// 回车登录
passWordEidt.KeyDown += (s, e) => { if (e.KeyCode == Keys.Enter) TryLogin(); };
// 左键点击 setPanel -> 修改密码(需当前密码验证通过)
setPanel.Click += (s, e) => {
if (e is MouseEventArgs me && me.Button == MouseButtons.Right) return;
DoChangePassword();
};
// 右键已经由 ContextMenuStrip 处理
}
#endregion
#region 登录/改密/重置
private void TryLogin()
{
string user = (userNameEdit.Text ?? "").Trim();
string pwd = passWordEidt.Text ?? "";
if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(pwd))
{
MessageBox.Show("请输入账号和密码。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (SecureUserStore.Validate(user, pwd))
{
SecureUserStore.SetLastUser(user); // 记住本次成功登录账号
this.DialogResult = DialogResult.OK;
this.Close();
}
else
{
MessageBox.Show("账号或密码错误。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
passWordEidt.SelectAll();
passWordEidt.Focus();
}
}
private void DoChangePassword()
{
string user = (userNameEdit.Text ?? "").Trim();
string currPwd = passWordEidt.Text ?? "";
if (string.IsNullOrEmpty(user))
{
MessageBox.Show("请先在账号框输入要修改密码的账号(例如:管理员、管理员1)。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
userNameEdit.Focus();
return;
}
if (!SecureUserStore.Exists(user))
{
MessageBox.Show("账号不存在,仅支持:管理员、管理员1。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
userNameEdit.Focus(); userNameEdit.SelectAll();
return;
}
// 必须先通过“账号+当前密码”验证
if (!SecureUserStore.Validate(user, currPwd))
{
MessageBox.Show("请先在密码框输入该账号的当前密码并验证通过,然后再修改。", "需要验证", MessageBoxButtons.OK, MessageBoxIcon.Warning);
passWordEidt.Focus(); passWordEidt.SelectAll();
return;
}
using (var dlg = new ChangePasswordDialog(user))
{
if (dlg.ShowDialog(this) == DialogResult.OK)
{
SecureUserStore.SetPassword(user, dlg.NewPassword);
MessageBox.Show("密码已更新并安全保存到本地。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
private void DoResetPassword()
{
// 仅管理员权限允许重置
if (!SecurityUtil.IsAdministrator())
{
MessageBox.Show("需要以【管理员身份运行】程序后才能重置密码。", "权限不足", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
using (var dlg = new ResetConfirmDialog())
{
if (dlg.ShowDialog(this) != DialogResult.OK) return;
// 校验重置口令(你可以修改 SecureReset.ResetSecret 自定义口令)
if (!SecureReset.VerifyResetCode(dlg.ResetCode))
{
MessageBox.Show("重置口令不正确。", "校验失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
string user = (userNameEdit.Text ?? "").Trim();
if (string.IsNullOrEmpty(user))
{
// 未指定账号 -> 询问是否重置所有内置账号
if (MessageBox.Show("未填写账号,是否重置所有内置账号为默认密码 123456?",
"重置确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
foreach (var u in SecureUserStore.BuiltinUsers) SecureUserStore.ResetToDefault(u);
MessageBox.Show("所有内置账号已重置为 123456。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
else
{
if (!SecureUserStore.Exists(user))
{
MessageBox.Show("账号不存在,无法重置。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
if (MessageBox.Show($"确定将「{user}」密码重置为 123456",
"重置确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
SecureUserStore.ResetToDefault(user);
MessageBox.Show("已重置为 123456。", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
#endregion
#region 工具:为控件设置 * 掩码(兼容 TextBox / DevExpress TextEdit
private static void SetPasswordMask(System.Windows.Forms.Control ctrl)
{
try
{
var t = ctrl.GetType();
var propPwdChar = t.GetProperty("PasswordChar");
if (propPwdChar != null && propPwdChar.CanWrite) propPwdChar.SetValue(ctrl, '*', null);
var propUseSys = t.GetProperty("UseSystemPasswordChar");
if (propUseSys != null && propUseSys.CanWrite) propUseSys.SetValue(ctrl, true, null);
var propProperties = t.GetProperty("Properties");
if (propProperties != null)
{
var propsObj = propProperties.GetValue(ctrl, null);
if (propsObj != null)
{
var px = propsObj.GetType();
var p1 = px.GetProperty("PasswordChar"); if (p1 != null && p1.CanWrite) p1.SetValue(propsObj, '*', null);
var p2 = px.GetProperty("UseSystemPasswordChar"); if (p2 != null && p2.CanWrite) p2.SetValue(propsObj, true, null);
}
}
}
catch { /* ignore */ }
}
#endregion
}
#region 安全工具 & 重置口令校验
internal static class SecurityUtil
{
public static bool IsAdministrator()
{
try
{
using (var identity = WindowsIdentity.GetCurrent())
{
var principal = new WindowsPrincipal(identity);
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
}
catch { return false; }
}
public static string Sha256(string s)
{
using (var sha = SHA256.Create())
{
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(s ?? ""));
var sb = new StringBuilder(bytes.Length * 2);
foreach (var b in bytes) sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
}
internal static class SecureReset
{
// ★ 重置口令(明文)。建议你改成自己的:仅内部人员知悉。
private const string ResetSecret = "Lskj#Reset2025";
private static readonly string ResetHash = SecurityUtil.Sha256(ResetSecret);
public static bool VerifyResetCode(string input)
=> string.Equals(SecurityUtil.Sha256(input ?? ""), ResetHash, StringComparison.OrdinalIgnoreCase);
}
#endregion
#region 本地安全存储(DPAPI 加密 + 记住上次账号)
internal static class SecureUserStore
{
// %LOCALAPPDATA%\Lskj.GetSecretKey\
private static readonly string AppDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Lskj.GetSecretKey");
private static readonly string StorePath = Path.Combine(AppDir, "users.sec"); // 用户: 密文
private static readonly string LastUserPath = Path.Combine(AppDir, "lastuser.dat"); // 纯文本:上一次成功登录账号
private static readonly byte[] Entropy = Encoding.UTF8.GetBytes("Lskj.GetSecretKey|DPAPI|v1");
internal static readonly string[] BuiltinUsers = new[] { "管理员", "管理员1" }; // 供外部枚举使用
private const string DefaultPassword = "123456";
// 缓存:用户名 -> Base64(Protect(UTF8(pwd)))
private static readonly Dictionary<string, string> _cipherMap = new Dictionary<string, string>(StringComparer.Ordinal);
private static bool _initialized;
private static readonly object _lock = new object();
public static void EnsureInitialized()
{
if (_initialized) return;
lock (_lock)
{
if (_initialized) return;
Directory.CreateDirectory(AppDir);
if (!File.Exists(StorePath))
{
foreach (var u in BuiltinUsers) _cipherMap[u] = ProtectToBase64(DefaultPassword);
Persist();
}
else
{
Load();
bool changed = false;
foreach (var u in BuiltinUsers)
if (!_cipherMap.ContainsKey(u)) { _cipherMap[u] = ProtectToBase64(DefaultPassword); changed = true; }
if (changed) Persist();
}
_initialized = true;
}
}
public static bool Exists(string user) { EnsureInitialized(); return _cipherMap.ContainsKey(user); }
public static bool Validate(string user, string plainPwd)
{
EnsureInitialized();
if (!_cipherMap.TryGetValue(user, out var b64)) return false;
try { return UnprotectFromBase64(b64) == (plainPwd ?? ""); }
catch { return false; }
}
public static void SetPassword(string user, string newPwd)
{
EnsureInitialized();
if (string.IsNullOrEmpty(user)) return;
_cipherMap[user] = ProtectToBase64(newPwd ?? "");
Persist();
}
public static void ResetToDefault(string user)
{
EnsureInitialized();
if (string.IsNullOrEmpty(user)) return;
_cipherMap[user] = ProtectToBase64(DefaultPassword);
Persist();
}
public static void SetLastUser(string user)
{
try { File.WriteAllText(LastUserPath, user ?? "", Encoding.UTF8); }
catch { /* ignore */ }
}
public static string GetLastUser()
{
try { return File.Exists(LastUserPath) ? (File.ReadAllText(LastUserPath, Encoding.UTF8).Trim()) : ""; }
catch { return ""; }
}
private static void Load()
{
_cipherMap.Clear();
foreach (var line in File.ReadAllLines(StorePath, Encoding.UTF8))
{
if (string.IsNullOrWhiteSpace(line)) continue;
var idx = line.IndexOf('=');
if (idx <= 0) continue;
var key = line.Substring(0, idx);
var val = line.Substring(idx + 1);
if (!_cipherMap.ContainsKey(key)) _cipherMap[key] = val;
}
}
private static void Persist()
{
var sb = new StringBuilder();
foreach (var kv in _cipherMap) sb.Append(kv.Key).Append('=').Append(kv.Value).Append('\n');
File.WriteAllText(StorePath, sb.ToString(), Encoding.UTF8);
}
private static string ProtectToBase64(string plain)
{
var bytes = Encoding.UTF8.GetBytes(plain ?? "");
var cipher = ProtectedData.Protect(bytes, Entropy, DataProtectionScope.CurrentUser);
return Convert.ToBase64String(cipher);
}
private static string UnprotectFromBase64(string base64)
{
var cipher = Convert.FromBase64String(base64 ?? "");
var plain = ProtectedData.Unprotect(cipher, Entropy, DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plain);
}
}
#endregion
#region 修改密码对话框
internal sealed class ChangePasswordDialog : Form
{
private readonly string _user;
private TextBox txtNew, txtConfirm;
private Button btnOk, btnCancel;
public string NewPassword { get; private set; }
public ChangePasswordDialog(string user)
{
_user = user;
BuildUi();
}
private void BuildUi()
{
this.Text = $"修改密码 - {_user}";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.StartPosition = FormStartPosition.CenterParent;
this.ClientSize = new Size(340, 170);
this.MaximizeBox = false; this.MinimizeBox = false;
var lbl1 = new Label { Text = "新密码:", AutoSize = true, Location = new Point(20, 28) };
var lbl2 = new Label { Text = "确认新密码:", AutoSize = true, Location = new Point(20, 68) };
txtNew = new TextBox { Location = new Point(130, 24), Width = 180, UseSystemPasswordChar = true, PasswordChar = '*' };
txtConfirm = new TextBox { Location = new Point(130, 64), Width = 180, UseSystemPasswordChar = true, PasswordChar = '*' };
btnOk = new Button { Text = "确定", DialogResult = DialogResult.OK, Location = new Point(130, 110), Width = 80 };
btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel, Location = new Point(230, 110), Width = 80 };
btnOk.Click += (s, e) =>
{
if (string.IsNullOrEmpty(txtNew.Text))
{
MessageBox.Show("新密码不能为空。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.DialogResult = DialogResult.None; return;
}
if (txtNew.Text != txtConfirm.Text)
{
MessageBox.Show("两次输入的密码不一致。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.DialogResult = DialogResult.None; return;
}
NewPassword = txtNew.Text;
};
this.Controls.AddRange(new System.Windows.Forms.Control[] { lbl1, txtNew, lbl2, txtConfirm, btnOk, btnCancel });
this.AcceptButton = btnOk; this.CancelButton = btnCancel;
}
}
#endregion
#region 重置口令对话框
internal sealed class ResetConfirmDialog : Form
{
private TextBox txtCode;
private Button btnOk, btnCancel;
public string ResetCode { get; private set; }
public ResetConfirmDialog() { BuildUi(); }
private void BuildUi()
{
this.Text = "输入重置口令";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.StartPosition = FormStartPosition.CenterParent;
this.ClientSize = new Size(360, 150);
this.MaximizeBox = false; this.MinimizeBox = false;
var lbl = new Label { Text = "重置口令:", AutoSize = true, Location = new Point(20, 30) };
txtCode = new TextBox { Location = new Point(100, 26), Width = 220, UseSystemPasswordChar = true, PasswordChar = '*' };
btnOk = new Button { Text = "确定", DialogResult = DialogResult.OK, Location = new Point(160, 80), Width = 75 };
btnCancel = new Button { Text = "取消", DialogResult = DialogResult.Cancel, Location = new Point(245, 80), Width = 75 };
btnOk.Click += (s, e) =>
{
if (string.IsNullOrEmpty(txtCode.Text))
{
MessageBox.Show("请输入重置口令。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.DialogResult = DialogResult.None; return;
}
ResetCode = txtCode.Text;
};
this.Controls.AddRange(new System.Windows.Forms.Control[] { lbl, txtCode, btnOk, btnCancel });
this.AcceptButton = btnOk; this.CancelButton = btnCancel;
}
}
#endregion
}