如何在PHP(Redis存储Session)中锁定验证码并发请求验证
解决PHP+Redis Session验证码并发验证漏洞的方案
你的问题核心在于验证码验证与Session销毁的操作不是原子性的:当大量并发请求同时到达时,所有请求都能在Session内的验证码被销毁前读取到有效值,导致全部验证通过,违背了“仅第一个请求有效”的预期。以下是针对PHP+Redis环境的可行解决方案:
1. 利用Redis原子命令实现“读取+销毁”原子操作
Redis支持原子性命令,可将读取验证码与销毁验证码的操作合并为一个原子步骤,确保只有第一个请求能获取到有效验证码。
方案A:使用GETDEL命令(Redis 6.2+)
GETDEL会原子性地读取键值并删除该键,直接解决并发读取问题:
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); // 拼接Redis中存储验证码的键(需和你存储Session验证码的键规则一致) $captchaRedisKey = 'PHPREDIS_SESSION:' . session_id() . ':captcha'; // 原子读取并删除验证码 $storedCaptcha = $redis->getDel($captchaRedisKey); if ($storedCaptcha && $storedCaptcha === $_POST['captcha']) { // 验证成功逻辑 } else { // 验证失败逻辑(包括无验证码、验证码不匹配、重复提交) }
方案B:用事务实现低版本Redis兼容(Redis <6.2)
如果你的Redis版本不支持GETDEL,可以用Redis事务保证GET+DEL的原子性:
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); $captchaRedisKey = 'PHPREDIS_SESSION:' . session_id() . ':captcha'; // 开启事务,原子执行读取和删除 $redis->multi(); $redis->get($captchaRedisKey); $redis->del($captchaRedisKey); $transactionResults = $redis->exec(); $storedCaptcha = $transactionResults[0]; if ($storedCaptcha && $storedCaptcha === $_POST['captcha']) { // 验证成功逻辑 } else { // 验证失败逻辑 }
2. 用原子锁标记验证码已使用
通过Redis的SETNX命令(仅当键不存在时设置成功)给验证码添加一次性使用标记,确保只有第一个请求能进入验证流程:
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); $captchaRedisKey = 'PHPREDIS_SESSION:' . session_id() . ':captcha'; $usedFlagKey = 'PHPREDIS_SESSION:' . session_id() . ':captcha_used'; // 原子性设置已使用标记,只有第一个请求能成功 if ($redis->setnx($usedFlagKey, 1)) { $storedCaptcha = $redis->get($captchaRedisKey); // 验证完成后清理所有相关键 $redis->del($captchaRedisKey, $usedFlagKey); if ($storedCaptcha === $_POST['captcha']) { // 验证成功逻辑 } else { // 验证码不匹配逻辑 } } else { // 已有请求验证过,直接返回失败 }
3. 临时应急:开启Session阻塞锁(不推荐)
如果暂时无法修改Redis操作逻辑,可以尝试开启PHP Session的阻塞锁,强制同一Session的请求串行处理,但会牺牲并发性能:
// 在session_start()前设置 ini_set('session.lock_timeout', 1); ini_set('session.use_strict_mode', 1); session_start(); // 后续验证逻辑不变
注意:该方案依赖PHP Redis扩展的锁实现,不同版本兼容性可能有差异,仅作为临时过渡方案。
关键注意事项
- 确保验证码与Session严格绑定,避免不同Session共享验证码键。
- 验证完成后必须清理Redis中的验证码及相关标记键,避免垃圾数据积累。
- 用并发测试工具(如ab、wrk)验证修复效果,确认仅第一个请求能通过验证。
内容的提问来源于stack exchange,提问作者Venk V
相关产品推荐
相关产品推荐

