Laravel如何设置登录失败后自动锁定账户,防止多次输错密码?
- 内容介绍
- 文章标签
- 相关推荐
本文共计758个文字,预计阅读时间需要4分钟。
默认不锁定,必须手动添加逻辑——Laravel 自带的 Auth 机制完全处理登录失败次数和账户锁定。
为什么 AttemptLogin 不会自动锁账号
Laravel 的 attempt 方法只校验凭证,成功就发 token 或写 session,失败就返回 false,连日志都不记。它不知道“这是第几次错”,更不会查数据库里有没有超限。
- 所有失败计数、时间窗口、锁定状态都得自己存(推荐用缓存 + 用户字段组合)
-
throttle中间件只限制请求频率(IP 或用户),不是按账号维度做“输错 5 次就锁 15 分钟” - 如果你只靠
RateLimiter::attempt,那锁的是 IP,不是用户,换网络就能绕过
怎么实现“输错 N 次锁 M 分钟”
核心是三件事:记录失败、判断是否超限、拦截后续登录。别碰数据库主表字段(比如加个 locked_at),用缓存更轻量也更准。
- 用
Cache::put("login_fail_{$username}", $count, $ttl)记次数,$ttl设为锁定总时长(如 900 秒) - 每次登录前先
Cache::get("login_fail_{$username}"),有值且 ≥ 阈值就直接throw new LockoutException - 成功登录后,务必
Cache::forget("login_fail_{$username}"),否则下次登不进 - 如果要用数据库兜底(比如需要审计),在缓存失效时读
failed_login_attempts字段,但别每回都查库
示例片段:
if ($attempts = Cache::get("login_fail_{$request->input('email')}")) { if ($attempts >= 5) { throw new LockoutException('Account locked for 15 minutes.'); } } // ... attempt login if (! $result) { Cache::put("login_fail_{$request->input('email')}", $attempts + 1, 900); }
容易被忽略的边界情况
真实项目里,这些点一漏就白做了:
- 用户名大小写不敏感?
$request->input('email')要strtolower()再拼 key,否则Admin@Ex.com和admin@ex.com算两个账号 - 没清空登录成功后的缓存,用户第一次登错 4 次,第五次登对了,第六次再错又从 1 开始计——锁不住
- 忘记在
LockoutException对应的响应里返回 429 状态码,前端收不到明确信号,重试逻辑乱套 - 用 Redis 做缓存时,如果没配持久化或内存满,
Cache::get可能返回 null,导致误判“没锁”
真正麻烦的不是写几行计数逻辑,而是把“谁、什么时候、因为什么被锁”和“什么时候自动解”在分布式环境下对齐。缓存失效时间、服务重启、多台机器共享状态——这些地方出问题,锁就形同虚设。
本文共计758个文字,预计阅读时间需要4分钟。
默认不锁定,必须手动添加逻辑——Laravel 自带的 Auth 机制完全处理登录失败次数和账户锁定。
为什么 AttemptLogin 不会自动锁账号
Laravel 的 attempt 方法只校验凭证,成功就发 token 或写 session,失败就返回 false,连日志都不记。它不知道“这是第几次错”,更不会查数据库里有没有超限。
- 所有失败计数、时间窗口、锁定状态都得自己存(推荐用缓存 + 用户字段组合)
-
throttle中间件只限制请求频率(IP 或用户),不是按账号维度做“输错 5 次就锁 15 分钟” - 如果你只靠
RateLimiter::attempt,那锁的是 IP,不是用户,换网络就能绕过
怎么实现“输错 N 次锁 M 分钟”
核心是三件事:记录失败、判断是否超限、拦截后续登录。别碰数据库主表字段(比如加个 locked_at),用缓存更轻量也更准。
- 用
Cache::put("login_fail_{$username}", $count, $ttl)记次数,$ttl设为锁定总时长(如 900 秒) - 每次登录前先
Cache::get("login_fail_{$username}"),有值且 ≥ 阈值就直接throw new LockoutException - 成功登录后,务必
Cache::forget("login_fail_{$username}"),否则下次登不进 - 如果要用数据库兜底(比如需要审计),在缓存失效时读
failed_login_attempts字段,但别每回都查库
示例片段:
if ($attempts = Cache::get("login_fail_{$request->input('email')}")) { if ($attempts >= 5) { throw new LockoutException('Account locked for 15 minutes.'); } } // ... attempt login if (! $result) { Cache::put("login_fail_{$request->input('email')}", $attempts + 1, 900); }
容易被忽略的边界情况
真实项目里,这些点一漏就白做了:
- 用户名大小写不敏感?
$request->input('email')要strtolower()再拼 key,否则Admin@Ex.com和admin@ex.com算两个账号 - 没清空登录成功后的缓存,用户第一次登错 4 次,第五次登对了,第六次再错又从 1 开始计——锁不住
- 忘记在
LockoutException对应的响应里返回 429 状态码,前端收不到明确信号,重试逻辑乱套 - 用 Redis 做缓存时,如果没配持久化或内存满,
Cache::get可能返回 null,导致误判“没锁”
真正麻烦的不是写几行计数逻辑,而是把“谁、什么时候、因为什么被锁”和“什么时候自动解”在分布式环境下对齐。缓存失效时间、服务重启、多台机器共享状态——这些地方出问题,锁就形同虚设。

