验证码不刷新是浏览器缓存所致,需php端加no-cache响应头并前端img src动态添加时间戳或随机参数。

验证码图片不刷新,其实是浏览器在“自作主张”
用户点“换一张”,图片没变,但后台日志显示 captcha.php 已执行多次、$_SESSION['captcha_code'] 值也在更新——这不是代码错了,是浏览器把图片响应缓存了。
根本原因在于:PHP 脚本输出 image/png 时没发禁用缓存的头,Chrome/Firefox 就按启发式规则缓存几秒到几分钟;CDN 或 Nginx 反代更狠,可能默认缓存 5 分钟。
- 必须在
captcha.php开头加三行:header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');、header('Pragma: no-cache');、header('Expires: 0'); - 前端
<img>的src不能写死为captcha.php,得动态加时间戳或随机参数,比如captcha.php?t=<?php echo time(); ?>或captcha.php?_=<?php echo uniqid(); ?> -
session_regenerate_id(true)防会话固定,但它不解决缓存问题——别混用这两个目标
ThinkPHP 里用 Cache::tag() 做限流,为什么总失效
因为默认文件驱动下,Cache::tag() 的 get 和 set 是非原子操作。并发请求同时读到旧值,都判断“还没超限”,结果一起通过——漏判不是偶然,是必然。
不是换写法就能救,得先确认缓存驱动真连上了 Redis:
- 检查
config/cache.php中'default' => 'redis',且'redis'配置块存在、PING得通 - 限流 key 必须带唯一标识,例如
'rate_limit_' . $request->ip() . '_' . $action,不能只用$action - 改用
$cache->inc($key):Redis 驱动下它是原子的;文件驱动不支持,会静默降级为get+set,务必提前验证 -
inc()第一次调用返回 1,所以判断逻辑得是if ($count > $limit),不是>=
短信发送接口被刷爆,单靠前端倒计时毫无意义
前端 60 秒倒计时 + disabled 按钮,对黑产工具来说等于没有。真正起作用的只有服务端原子校验——而且必须用 Lua 封装 INCR+GET+EXPIRE,否则两个并发请求同时读到 0,都会通过。
键名设计也有坑:
- 别用
sms:mobile:{$mobile}这种冒号开头的格式,某些 Redis 驱动下会导致KEYS sms:*批量失效 - 推荐
sms_mobile_{$mobile}和sms_ip_{$ip},TTL 设 60 秒,但注意:第 59 秒再点一次要拒绝,不是重置计时器 - IP 黑名单单独建 key,如
sms_ip_blacklist:{$ip},TTL 设 3600 秒;但真实 NAT 环境下几百人共用一个出口 IP,慎用永久封禁 - Redis Lua 脚本里必须先查黑名单,再查手机号频次,最后查 IP 频次——顺序错一步,风控就漏一层
验证码 + 限流组合策略,怎么避免办公网用户被误杀
纯按 IP 限流,在企业内网、校园网场景下极易误伤;只靠图形验证码,又挡不住 OCR 自动识别。得用分层响应,而不是一刀切。
轻量请求放行,可疑行为逐步加压:
- 第一层:对
/api/login这类敏感接口,先查 IP 最近 60 秒请求数,超 5 次直接返回429,不给验证码机会 - 第二层:未超阈值但
User-Agent是空、含python-requests或curl特征,强制要求提交captcha_token字段,否则400 - 第三层:验证码连续校验失败 3 次,对该 IP 写入
block_{$ip},TTL 900 秒,后续请求直接拦截 - 所有限流 key 建议加时间分片,例如
req_'.date('i').'_'.$ip,避免单 key 过热拖垮 Redis
最常被忽略的一点:验证码校验失败后,不要只删 session 里的 code——得同步清 Redis 里对应 IP 的计数器,否则下次请求仍从旧值继续累加。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











