diff --git a/admin/dashboard/blueprints/auth.py b/admin/dashboard/blueprints/auth.py index 659aedd..bcf610a 100644 --- a/admin/dashboard/blueprints/auth.py +++ b/admin/dashboard/blueprints/auth.py @@ -54,18 +54,32 @@ def login(): return jsonify({"success": False, "error": error}), 429 return render_template('login.html', error=error) - # 优先使用数据库账号体系鉴权;若不可用则回退旧配置模式,保证兼容存量部署。 + # 优先使用数据库账号体系鉴权。 + # + # 这里特意不再做“只要数据库校验失败就回退本地配置密码”的粗暴兜底, + # 原因有两点: + # 1. 一旦数据库里已经存在该管理员账号,真实口令状态应当以数据库为准; + # 2. 如果继续无条件回退本地 config.toml,就会出现“数据库已改密,但本地默认密码仍然生效” + # 的安全问题,也会让弱密码提示误判。 + # + # 兼容策略调整为: + # - 数据库可用且账号存在:只认数据库; + # - 数据库不可用,或数据库里根本没有这个账号:才回退旧配置模式。 login_ok = False + db_account_exists = False if admin_db: try: - login_ok = admin_db.verify_admin_password(username, password) + db_account_exists = bool(admin_db.get_admin_by_username(username)) + if db_account_exists: + login_ok = admin_db.verify_admin_password(username, password) if login_ok: admin_db.mark_login_success(username, request.remote_addr or "") except Exception as e: logger.error(f"数据库账号登录校验异常,回退配置模式: {e}") login_ok = False + db_account_exists = False - if not login_ok: + if not login_ok and not db_account_exists: login_ok = (username == server.username and password == server.password) if login_ok: @@ -107,11 +121,18 @@ def logout(): @login_required def get_security_status(): """返回当前登录管理员的安全状态。""" + server = current_app.dashboard_server + username = str(session.get("username", "") or "").strip() + # 安全状态这里改成“实时重算 + 回写 session”,而不是只读登录瞬间写入的旧值: + # 1. 避免管理员已经在数据库里改了密码,但当前会话仍保留旧的 force_password_change 标记; + # 2. 避免本地配置和数据库状态不一致时,前端一直弹出错误的弱密码提示。 + force_password_change = bool(server.should_force_password_change(username)) if username else False + session["force_password_change"] = force_password_change return jsonify({ "success": True, "data": { - "force_password_change": bool(session.get("force_password_change", False)), - "session_timeout_minutes": int(current_app.dashboard_server.get_auth_policy().get("session_timeout_minutes", 480)), + "force_password_change": force_password_change, + "session_timeout_minutes": int(server.get_auth_policy().get("session_timeout_minutes", 480)), } }) diff --git a/admin/dashboard/server.py b/admin/dashboard/server.py index 40bf19f..02c2b6b 100644 --- a/admin/dashboard/server.py +++ b/admin/dashboard/server.py @@ -307,16 +307,35 @@ class DashboardServer: def should_force_password_change(self, username: str) -> bool: """判断当前管理员是否应该被强制提示修改密码。""" admin_db = getattr(self, "admin_account_db", None) - if admin_db and admin_db.is_using_risky_password(username): - return True + normalized_username = str(username or "").strip() + if not normalized_username: + return False + + # 判断弱密码时优先且尽量只相信数据库中的真实账号状态。 + # + # 之前的问题在于: + # 1. 代码先看数据库,再继续拿本地 config.toml 的默认用户名/密码做补充判断; + # 2. 如果数据库中的管理员已经改成强密码,但本地配置仍保留 admin/admin123, + # 前端仍会被误判成“当前账号在使用弱密码”; + # 3. 这会让后台改密提示和真实账号状态脱节。 + # + # 现在的策略改成: + # - 数据库里存在该管理员账号:只按数据库口令哈希判断; + # - 数据库里不存在该账号,才允许回退到旧配置模式,兼容尚未迁移完成的老部署。 + if admin_db: + try: + admin_row = admin_db.get_admin_by_username(normalized_username) + if admin_row is not None: + return admin_db.is_using_risky_password(normalized_username) + except Exception as e: + # 安全提示属于辅助能力,数据库偶发异常时不应因为误判把用户“锁”在改密弹窗里。 + # 因此这里记录 warning,并继续走兼容兜底,而不是直接强制提示弱密码。 + self.LOG.warning(f"读取后台账号安全状态失败,将尝试兼容兜底判断: username={normalized_username}, error={e}") - # 数据库体系不可用时,再回退配置值判断,至少把默认 admin/admin123 识别出来。 fallback_username = str(self.username or "").strip() fallback_password = str(self.password or "").strip() - return ( - str(username or "").strip() == fallback_username - and fallback_password in getattr(admin_db, "RISKY_PASSWORDS", {"admin123", "admin"}) - ) + risky_passwords = getattr(admin_db, "RISKY_PASSWORDS", {"admin123", "admin"}) + return normalized_username == fallback_username and fallback_password in risky_passwords def _register_blueprints(self, app): """注册所有蓝图"""