129 lines
5.6 KiB
PHP
129 lines
5.6 KiB
PHP
<?php
|
|
namespace App\Controllers\Admin;
|
|
|
|
use App\Controllers\Controller;
|
|
use App\Models\AdminUser;
|
|
use Core\App;
|
|
|
|
class AuthController extends Controller
|
|
{
|
|
protected $layout = 'layouts/admin';
|
|
|
|
public function login()
|
|
{
|
|
if (is_admin()) { $this->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');
|
|
}
|
|
}
|