redirect(login_landing()); } $error = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { // 质量红线:登录必须校验 CSRF + 验证码 + IP/会话双重失败限速,杜绝机器人暴力破解 $ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (!csrf_check()) { $error = '表单已过期,请刷新页面后重试'; } elseif (ip_login_blocked($ip)) { $error = '尝试次数过多,请 15 分钟后再试'; } elseif (!captcha_check($this->post('captcha'))) { $error = '验证码错误,请重新计算'; } elseif ($this->isBlocked()) { $error = '尝试次数过多,请 15 分钟后再试'; } else { $u = trim($this->post('username')); $p = $this->post('password'); $ok = false; $user = null; $found = (new AdminUser())->byUsername($u); if ($found && (int)($found['status'] ?? 1) === 1 && password_verify($p, $found['password'])) { $ok = true; $user = $found; } elseif ($this->fallbackEnabled() && $u === \Core\App::config('admin.username') && $p === \Core\App::config('admin.password')) { // 配置文件兜底账号:仅本地/演示模式开启(本地规则要求生产禁用默认密码后门) $ok = true; $user = ['id' => 0, 'username' => $u, 'name' => '管理员', 'role' => 'super_admin']; } if ($ok) { $this->clearAttempts(); ip_login_clear($ip); session_regenerate_id(true); // 防会话固定 $_SESSION['admin_logged'] = true; $_SESSION['admin_id'] = $user['id'] ?? 0; $_SESSION['admin_name'] = $user['name'] ?? $u; $_SESSION['admin_role'] = $user['role'] ?? 'super_admin'; $_SESSION['crm_role'] = $user['crm_role'] ?? 'none'; $_SESSION['psi_role'] = $user['psi_role'] ?? 'none'; $dec = function ($v) { $a = @json_decode((string)$v, true); return is_array($a) ? $a : null; }; $_SESSION['crm_perms'] = $dec($user['crm_perms'] ?? null); $_SESSION['psi_perms'] = $dec($user['psi_perms'] ?? null); $this->redirect(login_landing()); } $this->registerAttempt(); ip_login_register($ip); $error = '用户名或密码错误'; } } $captcha = captcha_make(); // 每次渲染都发放新的算术验证码 return $this->view('admin/login', ['error' => $error, 'captcha' => $captcha]); } /** 配置文件兜底账号是否启用:生产(mysql 模式)默认关闭,本地演示可开 */ private function fallbackEnabled(): bool { return (bool) \Core\App::config('admin.allow_config_fallback', false) && \Core\App::config('app.driver', 'file') !== 'mysql'; } /** 暴力破解限速:单会话 15 分钟内失败 5 次即锁定 */ private function isBlocked(): bool { $t = $_SESSION['login_attempts'] ?? null; if (!$t || ($t['time'] + 900) < time()) return false; return $t['count'] >= 5; } private function registerAttempt(): void { $t = $_SESSION['login_attempts'] ?? ['count' => 0, 'time' => time()]; if (($t['time'] + 900) < time()) { $t = ['count' => 0, 'time' => time()]; } $t['count']++; $_SESSION['login_attempts'] = $t; } private function clearAttempts(): void { unset($_SESSION['login_attempts']); } /** 修改当前登录账号的密码 */ public function password() { admin_required(); $error = ''; $ok = ''; $uid = admin_uid(); if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (!csrf_check()) { $error = '表单已过期,请重试'; } else { $cur = $this->post('current_password'); $new = $this->post('new_password'); $confirm = $this->post('confirm_password'); $m = new AdminUser(); $me = $uid ? $m->find($uid) : null; if (!$me) { $error = '账号异常,请重新登录'; } elseif (!password_verify($cur, $me['password'])) { $error = '当前密码不正确'; } elseif (strlen($new) < 6) { $error = '新密码至少 6 位'; } elseif ($new !== $confirm) { $error = '两次输入的密码不一致'; } else { $m->update($uid, ['password' => password_hash($new, PASSWORD_DEFAULT)]); $ok = '密码修改成功'; } } } return $this->view('admin/password', ['error' => $error, 'ok' => $ok]); } public function logout() { unset($_SESSION['admin_logged'], $_SESSION['admin_name'], $_SESSION['admin_id'], $_SESSION['admin_role'], $_SESSION['crm_role'], $_SESSION['psi_role'], $_SESSION['crm_perms'], $_SESSION['psi_perms']); $this->redirect('admin/login'); } }