一、锁定机制说明
XiunoX 登录安全采用 双维度锁定 机制,前后台登录共用同一套规则。
1.1 两个锁定维度
维度 | 存储位置 | 触发条件 | 影响 |
|---|
uid 维度 | user 表 login_attempts + banned_until 字段
| 某用户密码连续错误达阈值 | 该用户无法登录(前台+后台) |
IP 维度 | user_login_log 表(实时统计)
| 某 IP 在锁定窗口内失败达阈值 | 该 IP 无法登录任何账号 |
1.2 关键配置项
后台路径:管理后台 → 安全设置→ 账号安全(admin/?security-account.htm)
后台字段 | 配置键 | 默认值 | 同步到 | 说明 |
|---|
密码错误重试次数 | security_password_max_retries
| 5 | login_max_attempts
| 连续失败多少次后锁定 |
锁定时间 | security_lockout_duration
| 15(分钟) | login_ban_duration
| 锁定持续多少分钟 |
单位换算:security_lockout_duration 单位是分钟,login_ban_duration 单位是秒。保存时 SecurityConfigService 自动 ×60 转换。
1.3 锁定流程
用户登录失败
↓
LoginSecurityService::recordAttempt()
↓
┌─────────────────────────────────────┐
│ uid 维度: │
│ user.login_attempts += 1 │
│ if(login_attempts >= max_attempts): │
│ user.banned_until = time + ban_duration │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ IP 维度(下次登录时检查): │
│ 统计 user_login_log 中该 IP 在 │
│ ban_duration 窗口内的失败次数 │
│ if(失败次数 >= max_attempts): │
│ 拒绝登录,返回 code=-1003 │
└─────────────────────────────────────┘
1.4 自动解锁
uid 维度:banned_until 时间戳到期后,下次登录时 LoginSecurityService::checkBan() 自动重置 login_attempts=0, banned_until=0
IP 维度:锁定窗口(ban_duration 秒)过期后,旧的失败记录不再计入统计,自动恢复
二、解锁方法
方法 1:等待自动解锁(推荐)
最简单的方式。默认配置下等待 15 分钟即可自动解锁。
方法 2:直接操作数据库(uid 维度解锁)
适用于管理员账号被锁、无法进入后台的情况。
2.1 查询锁定状态
-- 查看管理员账号的锁定状态(uid=1 通常是超级管理员)
SELECT uid, username, login_attempts, banned_until,
FROM_UNIXTIME(banned_until) AS unlock_time,
CASE WHEN banned_until > UNIX_TIMESTAMP() THEN '已锁定' ELSE '正常' END AS status
FROM bbs_user
WHERE uid = 1;
2.2 解锁单个用户
-- 重置失败次数和锁定时间
UPDATE bbs_user
SET login_attempts = 0, banned_until = 0
WHERE uid = 1;
2.3 解锁所有被锁用户
-- 批量解锁所有被锁账号
UPDATE bbs_user
SET login_attempts = 0, banned_until = 0
WHERE banned_until > 0;
方法 3:清除 IP 维度锁定记录
适用于某个 IP 被锁(比如管理员自己的固定 IP)。
3.1 查询 IP 失败记录
-- 查看 IP 失败次数(将 X.X.X.X 替换为实际 IP)
SELECT COUNT(*) AS fail_count
FROM bbs_user_login_log
WHERE ip = INET_ATON('X.X.X.X')
AND success = 0
AND time > UNIX_TIMESTAMP() - 900;
3.2 清除该 IP 的失败记录
-- 删除该 IP 在锁定窗口内的失败记录
DELETE FROM bbs_user_login_log
WHERE ip = INET_ATON('X.X.X.X')
AND success = 0
AND time > UNIX_TIMESTAMP() - 900;
3.3 清除所有失败记录(慎用)
-- 清空所有登录失败日志(影响所有用户的安全审计)
DELETE FROM bbs_user_login_log WHERE success = 0;
方法 4:通过 PHP 脚本解锁
如果无法直接操作数据库,可在服务器上创建临时 PHP 文件执行。
在 xiunobbs-master/ 目录下创建 tmp_unlock.php:
<?php
// 临时解锁脚本,用完立即删除!
// 访问 https://你的域名/tmp_unlock.php?uid=1 执行
define('APP_PATH', __DIR__ . '/');
include APP_PATH . 'index.php';
// 只允许管理员或通过 CLI 执行
if(php_sapi_name() !== 'cli') {
// 简单 token 校验,防止被恶意调用
$token = isset($_GET['token']) ? $_GET['token'] : '';
if($token !== '你的随机密钥') {
exit('Forbidden');
}
}
$uid = isset($_GET['uid']) ? intval($_GET['uid']) : 1;
// 调用 LoginSecurityService 重置
include_once APP_PATH . 'lib/LoginSecurityService.php';
LoginSecurityService::resetAttempts($uid);
echo "User $uid unlocked successfully.";
访问 https://你的域名/tmp_unlock.php?uid=1&token=你的随机密钥 执行,用完立即删除该文件。
三、修改锁定时间
方法 1:后台修改(推荐)
登录管理后台
进入 安全 → 账号安全(admin/?security-account.htm)
修改:
密码错误重试次数:默认 5 次
锁定时间:默认 15 分钟
点击保存
保存后 SecurityConfigService::save_config() 会自动同步到 conf/conf.php 的 login_max_attempts 和 login_ban_duration。
方法 2:直接修改配置文件
编辑 conf/conf.php:
'login_max_attempts' => 5, // 最大尝试次数
'login_ban_duration' => 900, // 锁定时长(秒),900=15分钟,1800=30分钟
修改后需清理 tmp/ 缓存。
四、管理员被锁的特殊处理
4.1 问题场景
管理员账号被锁后,无法登录后台修改安全设置或解锁其他用户。
4.2 解决方案
场景 | 解决方案 |
|---|
管理员账号被锁 | 方法 2(数据库解锁 uid=1)或 方法 4(PHP 脚本) |
管理员 IP 被锁 | 方法 3(清除 IP 记录)或换网络/IP 访问 |
同时被锁(uid+IP) | 先清除 IP 记录,再数据库解锁 uid |
无数据库权限 | 联系服务器管理员,或通过 FTP 创建 PHP 解锁脚本 |
4.3 紧急关闭登录锁定
如需临时关闭锁定机制(紧急情况),编辑 conf/conf.php:
// 设置极大值,实际关闭锁定
'login_max_attempts' => 999999,
'login_ban_duration' => 1,
警告:仅用于紧急恢复访问,恢复后应立即改回合理值。
五、相关数据表
5.1 user 表相关字段
字段 | 类型 | 说明 |
|---|
login_attempts
| int(11) | 连续失败次数,成功后重置为 0 |
banned_until
| int(11) | 锁定到期时间戳(UNIX),0 表示未锁定 |
last_login_ip
| int(11) | 最后登录 IP(ip2long) |
last_login_time
| int(11) | 最后登录时间戳 |
5.2 user_login_log 表
字段 | 类型 | 说明 |
|---|
uid
| int(11) | 用户 UID(不存在用户则为 0) |
ip
| int(11) | 登录 IP(ip2long) |
time
| int(11) | 登录时间戳 |
success
| tinyint(1) | 1=成功,0=失败 |
user_agent
| varchar(255) | 浏览器 UA |
六、相关代码位置
文件 | 说明 |
|---|
lib/LoginSecurityService.php | 锁定核心逻辑(checkBan/recordAttempt/checkIpBan/resetAttempts) |
lib/security/SecurityConfigService.php | 安全配置读写,同步 security_* → login_* |
admin/route/security.php | 后台账号安全设置页(account action) |
admin/view/htm/security_account.htm | 后台账号安全设置模板 |
admin/route/index.php | 后台登录路由(调用 checkBan/checkIpBan) |
route/user.php | 前台登录路由(调用 checkBan/checkIpBan) |
conf/conf.default.php | 默认配置(login_max_attempts=5, login_ban_duration=900) |
七、常见问题
Q1: 为什么管理员后台登录页没有验证码?
后台登录验证码按 IP 失败次数触发:某 IP 在锁定窗口内失败 ≥ 3 次时才显示验证码。首次访问不显示,防止正常使用时打扰。
Q2: 修改锁定时间后已锁定的用户会立即解锁吗?
不会。修改配置只影响新的锁定。已锁定用户的 banned_until 不会自动更新,需等待原锁定时间到期,或用方法 2 数据库手动解锁。
Q3: IP 维度锁定为什么没有 banned_until 字段?
IP 维度锁定是实时计算的:每次登录时查询 user_login_log 表中该 IP 在锁定窗口内的失败次数。窗口过期后旧记录自动不再计入,无需持久化字段。
Q4: 前台和后台的锁定是独立的吗?
不独立。前后台登录都调用 LoginSecurityService::checkBan($uid) 和 checkIpBan($longip),共用 user 表的 banned_until 字段。管理员在前台被锁,后台也无法登录。
Q5: 如何永久关闭登录锁定?
不推荐。如必须关闭,设置 conf/conf.php:
'login_max_attempts' => 999999,
'login_ban_duration' => 1,
这会让锁定几乎不触发,但会大幅降低安全性,容易遭受暴力破解。
Q6: 账号被锁后会退出前台吗?
会。系统在 index.inc.php 中检查 banned_until,账号被锁定时:
清除 $_SESSION['uid']
清除 bbs_token cookie
当前请求以游客身份处理
这样攻击者即使偷了前台 cookie,账号被锁后也无法持前台会话继续操作。管理员偶尔输错 1-2 次密码不受影响(仅达到锁定阈值如 5 次失败时才触发)。
Q7: 锁定后前台会话失效,解锁后能自动恢复吗?
不能。锁定导致前台 cookie 被清除,解锁后需要重新登录前台。这是设计预期,确保锁定期间攻击者无法利用残留会话。