登录失败锁定
登录失败锁定防两类攻击:密码爆破与恶意锁号(故意输错密码把他人账号锁死)。模板按「账号 + 客户端 IP」计数并内置基于参数的锁定策略,登录接口默认受保护。参考实现:shared/login-lock.service.ts。
锁定规则
| 配置 | 值 | 说明 |
|---|---|---|
| 计数维度 | 账号 + 客户端 IP | 同一账号在不同 IP 登录分别计数,互不影响 |
| 失败阈值 | 参数 sys.account.maxFailCount,默认 5 次 | 可在参数设置中在线调整 |
| 锁定时长 | 参数 sys.account.lockSeconds,默认 1800 秒 | 窗口为滑动式,每次失败刷新剩余时长 |
| 解锁方式 | 窗口到期自动解锁 / 登录成功立即清零 / 管理员监控页手动解锁 | 手动解锁与列表行一一对应,只清当前账号 + 来源的计数 |
| 驳回提示 | 密码错误次数过多,请 N 分钟后重试 | 业务异常,状态码 400 |
按「账号 + IP」计数的设计意图:攻击者的失败只累加到攻击者自己的来源上,单 IP 无法把他人正常登录锁死;每个 IP 独立享有 5 次额度,单源爆破同样被卡死。
实现机制
- 校验前置:
login()在校验验证码与密码之前先判定锁定状态,锁定中的请求直接驳回,不消耗验证码 - 原子计数:失败计数用 Redis
INCR原子自增,键格式login:fail:{username}:{ip},每次失败刷新过期时间(滑动窗口) - 验证码一次性:验证码 uuid 首次校验即作废(无论对错),防止脚本利用数学验证码的小答案空间在有效期内穷举
- 剩余提示:密码错误时返回剩余可尝试次数,便于用户感知风险
- 前置依赖:登录页图形验证码必须保持开启(参数
sys.account.captchaEnabled),否则脚本可低成本刷满失败次数
部署与真实 IP
客户端 IP 由 utils/ip.ts 的 getRequestIp 统一获取,规则是以 TCP 直连对端为准:对端不是内网地址(可信代理)时,请求携带的 X-Forwarded-For、X-Real-IP 一律视为不可信直接忽略,公网客户端无法通过伪造请求头绕过限流与锁定计数。
因此不同部署形态的要求不同:
- 后端端口直接对外(docker 端口映射直出等):无需任何配置,TCP 对端即真实客户端 IP
- 使用 Nginx 反向代理:必须让 Nginx 以覆盖式写法传递真实 IP,缺配置会导致所有用户共享代理 IP(限流与锁定集体误伤),配错成透传式则伪造头可穿透,参考配置:
nginx
location /api/ {
# 结尾不带斜杠:后端接口自带 /api 全局前缀,带斜杠会剥掉前缀导致 404
proxy_pass http://127.0.0.1:3000;
# 覆盖式写入,客户端伪造的同名头会被丢弃;勿用 $http_x_real_ip 透传或 $proxy_add_x_forwarded_for 追加
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size 50m;
proxy_read_timeout 60s;
}- Nginx 前还有 CDN / 云 WAF:Nginx 看到的 TCP 对端是 CDN 节点,真实 IP 需从 CDN 覆盖写入的请求头读取,同时服务端口只允许 CDN 回源段访问,防止绕过 CDN 直连后伪造
锁定监控
「系统监控 → 登录锁定」页面实时展示所有存在失败计数的来源(含尚未达到锁定阈值的,便于提前发现攻击苗头):
| 列 | 说明 |
|---|---|
| 登录账号 / 来源 IP | 与计数键 login:fail:{username}:{ip} 一一对应 |
| 失败次数 | 窗口期内累计失败次数 |
| 状态 | 未达到阈值为「未锁定」,达到阈值为「锁定中」 |
| 剩余可尝试 | 阈值减去失败次数,为 0 即已锁定 |
| 剩余时间 | 计数键的 TTL:未锁定时为计数窗口剩余时长,锁定中为解锁倒计时 |
操作列「解锁」删除当前行的计数键,只解除该账号在该来源的锁定,不影响其他来源。权限码:monitor:loginlock:query(列表)/ monitor:loginlock:unlock(解锁)。
操作留痕
- 锁定事件:登录日志天然记录每次失败与锁定驳回,按描述关键字「锁定」即可检索历史
- 解锁动作:操作日志以「解锁」操作类型(
sys_oper_type字典键值 8)自动记录操作人与目标账号,无需额外建表
注意事项
- 内网直连的客户端同样命中「可信代理」判定,理论上可伪造请求头骗过计数,风险面收窄在内网,通常可接受
- 限流(接口维度)与登录锁定(账号维度)是两道独立防线,前者按 IP + 接口计数兜底请求频率,后者按账号 + IP 计数阻断爆破,互不替代
- 滑动窗口意味着锁定期内继续失败会持续顺延解锁时间,用户应在锁定期间停止重试等待窗口自然过期
