验证码图片不显示是因PHP输出缓冲被破坏导致headers already sent;前后端分离下校验失败源于Session不共享,应改用带唯一标识的缓存方案替代session存储验证码。
验证码图片不显示:响应头被提前触发
最常见现象是浏览器显示空白、乱码或“无法加载图像”,
返回空字符串或报错
。根本原因不是代码写错了,而是 PHP 输出缓冲被破坏——只要在调用
前有任何输出(哪怕一个空格、
、
、
或 BOM 字符),GD 生成的二进制图像流就无法正确发送 header。
实操建议:
立即学习
“
PHP免费学习笔记(深入)
”;
验证码方法必须是独立接口,例如
,且路由显式定义为
该方法体内禁止任何调试语句、日志写入、
、
,连注释后多一个空行都可能出问题
检查控制器文件开头是否有 UTF-8 BOM(用编辑器如 VS Code 切换编码为 “UTF-8 无 BOM”)
确认 PHP 已启用
扩展:
,否则
会静默失败
前后端分离下校验总失败:Session 不共享
前端发请求获取验证码,再 POST 提交表单,但
总返回
,即使输入完全正确。这是因为 think-captcha 默认把验证码明文哈希存进 session,而前后端分离时,前端请求(如
)和后端 API(如
)跨域,默认不携带 Cookie,session ID 无法传递,后端根本查不到当初存的值。
实操建议:
立即学习
“
PHP免费学习笔记(深入)
”;
不要硬配 CORS +
强行复用 session——SameSite 策略、代理 cookie 路径、开发环境 localhost 端口差异都会导致不稳定
改用缓存方案:生成时用
得到
,再用
存 5 分钟,把
(如 UUID)返回给前端
提交时带上这个
和用户输入的
,后端用
取出原始值比对,注意统一转小写:
比对成功后立刻
清除,防止重放
API 模式下
返回数组而非图片
当你在
中把
后,
不再直接输出图片,而是返回形如
的数组。这是设计行为,不是 bug。若你仍按旧方式写
,前端 img 标签会收到 JSON 文本而非图片,自然无法渲染。
实操建议:
立即学习
“
PHP免费学习笔记(深入)
”;
开启 API 模式后,控制器中不能再用
直接响应图片请求
必须手动提取
并包装为合法响应:
更推荐做法:保持接口职责单一,用一个方法只返回 base64 图片(供前端直接
),另一个方法负责生成并缓存 code(供校验)
注意
前要清除 data URL 前缀,否则解码失败
验证码过期或重复使用:缓存 key 设计不当
用户刷新验证码多次后提交旧图,或者同一验证码被多人截获重放,都源于缓存 key 缺乏唯一性或未及时失效。比如全站共用一个 key
,或 key 仅基于时间戳没绑定设备/用户标识,攻击者就能暴力枚举或复用。
实操建议:
立即学习
“
PHP免费学习笔记(深入)
”;
key 必须带上下文标识:用手机号哈希、设备指纹、JWT 用户 ID(非 token 本身)、或一次性 UUID,例如
绝不把验证码明文塞进前端可读位置(如 localStorage、URL 参数、JWT payload)——它本就是低熵值的一次性凭证
校验前加时效判断:
,避免仅靠缓存 TTL 被绕过
校验成功后必须立即删除缓存:
,双保险防重放
实际最难处理的不是生成图片,而是让「生成」和「校验」这两个 HTTP 请求,在无 Cookie、无 Session 共享的前提下,还能安全地关联上。key 的构造方式、缓存清理时机、时效验证粒度,三者缺一不可。
Captcha::create()headers already sentCaptcha::create()echoLog::info()dump()public function captcha()get('captcha', 'index/captcha')var_dumpprint_rgdphp -m | grep gdCaptcha::create()Captcha::check($code)falsehttp://localhost:8080http://api.example.comwithCredentialsCaptcha::create()['code' => 'AB3X', 'img' => 'data:image/png;base64,...']cache($key, $code, 300)$key$key$codecache($key)strtolower($input) === strtolower(cache($key))cache($key, null)Captcha::create()config/captcha.php'api' => trueCaptcha::create()['code' => 'K9mP', 'img' => 'data:image/png;base64,...']return Captcha::create()return Captcha::create()$imgObj['img']return Response::create(base64_decode(str_replace("data:image/png;base64,", "", $imgObj['img'])), 'image/png');base64_decode'captcha''captcha_' . md5($request->ip() . $request->header('user-agent'))if (time() - cache($key . '_ts', 0) > 300) { return false; }cache($key, null); cache($key . '_ts', null);