Become a sponsor

说明
登录防爆破机制基于 Redis 实现同一 IP + 用户名维度的连续失败计数与锁定,防止暴力破解密码。配置项通过 .env 环境变量控制。
| 环境变量 | 默认值 | 说明 |
|---|---|---|
LOGIN_FAIL_MAX | 5 | 同一 IP+用户名连续失败次数上限 |
LOGIN_LOCK_SECONDS | 600 | 锁定时长(秒) |
1. 用户提交登录请求
2. 检查 Redis 中 login:lock:{ip}:{username} 是否存在
├─ 存在 → 返回"账号已锁定,请N秒后再试"
└─ 不存在 → 继续验证
3. 验证用户名密码
├─ 成功 → 清除失败计数,生成 Token
└─ 失败 → 累计 login:fail:{ip}:{username}
├─ 未达上限 → 返回"用户名或密码错误"
└─ 达到上限 → 设置锁定 key(TTL=LOGIN_LOCK_SECONDS),返回"账号已锁定"# src/modules/auth/service.py
async def login(self, handler):
ip = get_client_ip(handler.request)
username = handler.get_argument("username", "")
redis = get_redis()
# 检查锁定状态
lock_key = f"login:lock:{ip}:{username}"
is_locked = redis.get(lock_key)
if is_locked:
ttl = redis.ttl(lock_key)
return R.failed(handler, f"登录失败次数过多,请{ttl}秒后再试")
# 验证用户名密码
user = self.repo.get_one(username=username)
if not user or not verify_password(...):
# 累计失败次数
fail_key = f"login:fail:{ip}:{username}"
count = redis.incr(fail_key)
if count == 1:
redis.expire(fail_key, LOGIN_LOCK_SECONDS)
# 达到上限则锁定
if count >= LOGIN_FAIL_MAX:
redis.set(lock_key, 1, ex=LOGIN_LOCK_SECONDS)
return R.failed(handler, f"登录失败次数过多,请{LOGIN_LOCK_SECONDS}秒后再试")
return R.failed(handler, "用户名或密码错误")
# 登录成功 → 清除失败计数
redis.delete(f"login:fail:{ip}:{username}")
# 生成 Token ...| Key | 格式 | TTL | 说明 |
|---|---|---|---|
| 失败计数 | login:fail:{ip}:{username} | LOGIN_LOCK_SECONDS | 累计失败次数 |
| 锁定状态 | login:lock:{ip}:{username} | LOGIN_LOCK_SECONDS | 锁定标记 |
采用 IP + 用户名 双维度而非单纯 IP 维度,原因:
| 维度 | 优点 | 缺点 |
|---|---|---|
| 仅 IP | 简单 | 共享 IP(公司 NAT)下一人锁定影响所有人 |
| 仅用户名 | 精准 | 攻击者可从不同 IP 暴破同一账号 |
| IP + 用户名 | 精准且不影响他人 | Redis key 数量稍多 |
登录成功后清除该 IP+用户名 的失败计数,避免累计残留:
redis.delete(f"login:fail:{ip}:{username}")锁定 key 无需手动清除(有 TTL 自动过期)。
登录防爆破方案具备以下特点:
1. IP+用户名双维度:精准锁定,不影响共享 IP 下的其他用户
2. 阶梯式防护:累计失败次数,达到阈值自动锁定
3. 自动解锁:锁定 key 有 TTL,到期自动释放
4. 登录成功清除:成功登录后清除失败计数
5. Redis 驱动:高性能,支持分布式部署