v1.0.51: 批次1架构筑基 - 1)版本迁移(common/migrate.php+schema_versions+natsort迁移文件);2)登录安全(bcrypt平滑迁移login/user_add/user_update旧sha1命中自动重写+system_login_attempts限流+session加固httponly/samesite/secure);3)CSRF Token(服务端checkCsrf+auth/csrf.php登录页取token+前端common.js/login.js统一携带X-CSRF-Token);4)统一基座common/Api.php并存量强制迁移全部70个endpoint(Api::boot按public/super/module/permissions分流,写接口强制checkAjax+checkCsrf,全局异常处理);5)前端收敛(common.js新增esc转义别名);6)REV-4硬数据来源渠道可编辑(hard_update+补全弹窗下拉);7)prepared卫生(soft_update.php修复+analysis.php表名白名单REV-9)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+32
-13
@@ -3,18 +3,21 @@
|
||||
* 登录接口 POST /api/auth/login.php
|
||||
* 入参:username / password / slider_token(滑块验证通过后的token)
|
||||
* 出参(成功):{ code:0, msg:'登录成功', data:{ real_name, role_name, permissions } }
|
||||
*
|
||||
* 批次1 安全加固(REV-1/REV-8):
|
||||
* - 密码 bcrypt(password_hash)优先校验;存量 sha1 用户登录命中后平滑升级
|
||||
* - 登录限流(15分钟内同 IP+账号失败≥5 次锁定)
|
||||
* - 登录成功后 session_regenerate_id 防会话固定
|
||||
* - 经 Api::boot 统一走 CSRF + 安全 Session
|
||||
*/
|
||||
require_once __DIR__ . '/../common/db.php';
|
||||
require_once __DIR__ . '/../common/response.php';
|
||||
require_once __DIR__ . '/../common/logger.php';
|
||||
require_once __DIR__ . '/../common/Api.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
Response::error('请求方式错误', 400);
|
||||
}
|
||||
$pdo = Api::boot(['public' => true]);
|
||||
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = (string)($_POST['password'] ?? '');
|
||||
$sliderToken = trim($_POST['slider_token'] ?? '');
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
|
||||
// 滑块验证:前端 slider.js 拖动通过后生成的 token,服务端校验非空且格式合法
|
||||
if ($sliderToken === '' || strlen($sliderToken) < 10) {
|
||||
@@ -24,7 +27,9 @@ if ($username === '' || $password === '') {
|
||||
Response::error('请输入账号和密码');
|
||||
}
|
||||
|
||||
$pdo = DB::getInstance()->getPdo();
|
||||
// 登录限流(fail-open)
|
||||
checkLoginThrottle($username, $ip);
|
||||
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT u.*, r.role_name, r.permissions
|
||||
FROM system_users u
|
||||
@@ -34,22 +39,35 @@ $stmt = $pdo->prepare(
|
||||
$stmt->execute([$username]);
|
||||
$user = $stmt->fetch();
|
||||
|
||||
if (!$user || !hash_equals($user['password'], sha1($password))) {
|
||||
// 密码校验:bcrypt 优先;旧版无盐 sha1 校验通过后即时平滑升级为 bcrypt
|
||||
$authOk = false;
|
||||
if ($user) {
|
||||
if (password_verify($password, (string)$user['password'])) {
|
||||
$authOk = true;
|
||||
} elseif (hash_equals((string)$user['password'], sha1($password))) {
|
||||
$authOk = true;
|
||||
$pdo->prepare("UPDATE system_users SET password = ? WHERE id = ?")
|
||||
->execute([password_hash($password, PASSWORD_DEFAULT), (int)$user['id']]);
|
||||
}
|
||||
}
|
||||
if (!$authOk) {
|
||||
recordLoginAttempt($username, $ip, false);
|
||||
Response::error('账号或密码错误');
|
||||
}
|
||||
if ((int)$user['is_active'] !== 1) {
|
||||
recordLoginAttempt($username, $ip, false);
|
||||
Response::error('账号已被禁用,请联系管理员');
|
||||
}
|
||||
|
||||
recordLoginAttempt($username, $ip, true);
|
||||
|
||||
// 更新最后登录时间/IP
|
||||
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
$upd = $pdo->prepare("UPDATE system_users SET last_login_time = NOW(), last_login_ip = ? WHERE id = ?");
|
||||
$upd->execute([$ip, $user['id']]);
|
||||
|
||||
// 写入 Session
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
// 会话固定防护:登录成功后重新生成 session id(保留已有 CSRF Token)
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION['user_id'] = (int)$user['id'];
|
||||
$_SESSION['username'] = $user['username'];
|
||||
$_SESSION['real_name'] = $user['real_name'] ?? $user['username'];
|
||||
@@ -64,4 +82,5 @@ Response::success([
|
||||
'real_name' => $_SESSION['real_name'],
|
||||
'role_name' => $_SESSION['role_name'],
|
||||
'permissions' => $_SESSION['permissions'],
|
||||
'csrf_token' => csrfToken(),
|
||||
], '登录成功');
|
||||
|
||||
Reference in New Issue
Block a user