Revert "修复后台弱密码提示误判并恢复server.py编码"

This reverts commit 342b4c0065.
This commit is contained in:
Liu
2026-05-01 12:45:35 +08:00
parent 5aca5c5f28
commit 22c871105a
4 changed files with 251 additions and 467 deletions

View File

@@ -3,10 +3,9 @@
后台管理员账号数据访问层。
设计目标:
1. 用数据库表承载后台账号,逐步替代固定配置文件中的账号密码;
2. 提供安全的密码哈希存储与校验能力;
3. 支持登录成功后的登录信息回写,以及在线修改密码
4. 兼容历史部署,把旧配置中的管理员密码平滑迁移到数据库体系。
1. 用数据库表承载后台账号,替代固定配置文件账号密码
2. 提供安全的密码散列存储与校验能力;
3. 支持登录成功后的登录信息回写在线修改密码
"""
import base64
@@ -22,14 +21,14 @@ from db.base import BaseDBOperator
class AdminAccountDBOperator(BaseDBOperator):
"""后台管理员账号数据访问对象。"""
# 口令哈希算法前缀,便于后续平滑升级算法。
# 口令哈希算法版本前缀,便于将来平滑升级算法。
HASH_SCHEME = "pbkdf2_sha256"
# PBKDF2 迭代次数在安全性计算开销之间做平衡。
# PBKDF2 迭代次数在安全性计算开销之间做平衡。
HASH_ITERATIONS = 150_000
# 已知高风险密码列表
# 1. 这里只覆盖默认密码和常见弱密码,不做完整字典
# 2. 后台安全提醒只需要识别“明显高风险”的情况即可
# 3. 统一收敛在数据层,方便登录、改密、首提醒共用。
# 风险口令清单
# 1. 这里优先覆盖系统默认口令和常见极弱口令
# 2. 后台安全判断只需要识别“明显险”的情况,不追求做成完整密码字典
# 3. 统一在数据层,便于登录鉴权、修改密、首提醒共用。
RISKY_PASSWORDS = {
"admin",
"admin123",
@@ -41,7 +40,10 @@ class AdminAccountDBOperator(BaseDBOperator):
}
def init_tables(self) -> bool:
"""初始化后台管理员账号表。"""
"""初始化后台管理员表。
表名使用 t_admin_ 前缀,满足后台账号体系命名约定。
"""
sql = """
CREATE TABLE IF NOT EXISTS t_admin_accounts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -72,17 +74,12 @@ class AdminAccountDBOperator(BaseDBOperator):
)
def ensure_default_admin(self, username: str, password: str, display_name: str = "系统管理员") -> bool:
"""确保默认管理员存在,并在安全前提下补齐旧配置密码迁移
"""确保默认管理员存在。
这里的迁移策略分三层
1. 数据库里还没有管理员账号时,按当前配置创建初始账号
2. 数据库里已经有账号,且密码本来就和当前配置一致时,直接视为已同步
3. 数据库里仍是历史默认弱密码,但配置里已经换成了更强密码时,
自动把强密码同步进数据库,避免登录和弱密码提示长期错位。
注意:
只有在“数据库当前密码仍然是已知弱密码,而传入配置密码不是弱密码”时,
才允许用配置回写数据库;如果用户已经在后台主动改过密码,则继续以数据库为准。
行为约束
1. 若用户名已存在,不覆盖既有密码
2. 仅在“表里不存在该账号”时创建初始账号
3. 方便从旧配置平滑迁移到数据库账号体系。
"""
normalized_username = str(username or "").strip()
normalized_password = str(password or "").strip()
@@ -90,25 +87,17 @@ class AdminAccountDBOperator(BaseDBOperator):
return False
existing = self.get_admin_by_username(normalized_username)
if not existing:
password_hash = self.hash_password(normalized_password)
return self.execute_update(
"""
INSERT INTO t_admin_accounts (username, password_hash, display_name, status)
VALUES (%s, %s, %s, 1)
""",
(normalized_username, password_hash, str(display_name or "").strip() or normalized_username),
)
stored_hash = str(existing.get("password_hash") or "")
if stored_hash and self.verify_password(normalized_password, stored_hash):
if existing:
return True
current_password_is_risky = self.is_password_hash_using_risky_password(stored_hash)
incoming_password_is_risky = normalized_password.lower() in self.RISKY_PASSWORDS
if current_password_is_risky and not incoming_password_is_risky:
return self.update_password(normalized_username, normalized_password)
return True
password_hash = self.hash_password(normalized_password)
return self.execute_update(
"""
INSERT INTO t_admin_accounts (username, password_hash, display_name, status)
VALUES (%s, %s, %s, 1)
""",
(normalized_username, password_hash, str(display_name or "").strip() or normalized_username),
)
def verify_admin_password(self, username: str, password: str) -> bool:
"""校验账号口令是否正确。"""
@@ -146,22 +135,17 @@ class AdminAccountDBOperator(BaseDBOperator):
)
def is_using_risky_password(self, username: str) -> bool:
"""判断指定管理员是否仍在使用已知弱密码"""
"""判断指定管理员是否仍在使用风险口令"""
row = self.get_admin_by_username(username)
if not row:
return False
stored_hash = str(row.get("password_hash") or "")
return self.is_password_hash_using_risky_password(stored_hash)
@classmethod
def is_password_hash_using_risky_password(cls, stored_hash: str) -> bool:
"""判断一个已存储密码哈希是否仍然对应已知弱密码。"""
if not stored_hash:
return False
# 哈希无法反解,这里只能把已知高风险候选逐个比对。
for candidate in cls.RISKY_PASSWORDS:
if cls.verify_password(candidate, stored_hash):
# 口令是哈希存储的,因此只能把风险候选逐个比对。
for candidate in self.RISKY_PASSWORDS:
if self.verify_password(candidate, stored_hash):
return True
return False
@@ -170,10 +154,10 @@ class AdminAccountDBOperator(BaseDBOperator):
"""校验密码强度,返回错误提示;通过时返回 None。"""
password_text = str(raw_password or "")
if len(password_text) < 8:
return "新密码长度不能少于 8 "
return "新密码长度不能少于8"
if password_text.lower() in cls.RISKY_PASSWORDS:
return "新密码过于简单,请避免使用默认口令或常见弱密码"
return "新密码过于简单,请避免使用默认口令或常见弱口令"
score = 0
if re.search(r"[A-Za-z]", password_text):
@@ -183,7 +167,7 @@ class AdminAccountDBOperator(BaseDBOperator):
if re.search(r"[^A-Za-z0-9]", password_text):
score += 1
# 至少满足两类字符,既兼顾安全性,也避免把规则设得过于苛刻。
# 至少满足两类字符,既兼顾安全性,也避免把规则设得过于苛刻。
if score < 2:
return "新密码需至少包含字母、数字、符号中的两类"
return None
@@ -193,7 +177,7 @@ class AdminAccountDBOperator(BaseDBOperator):
"""生成口令哈希。
存储格式:
pbkdf2_sha256$迭代次数$盐HEX$摘要base64
pbkdf2_sha256$迭代次数$盐(HEX)$哈希(base64)
"""
password_text = str(raw_password or "")
salt_bytes = secrets.token_bytes(16)
@@ -213,7 +197,7 @@ class AdminAccountDBOperator(BaseDBOperator):
安全细节:
1. 使用 hmac.compare_digest避免时序侧信道问题
2. 对格式异常统一返回 False避免异常中断登录流程。
2. 对格式异常统一返回 False避免抛错打断登录流程。
"""
try:
scheme, iterations_text, salt_hex, digest_b64 = str(stored_hash or "").split("$", 3)