最常见原因是会话未启动或跨请求丢失;需在captcha.php和login.php中均调用session_start(),确保同域同配置,用hash_equals安全比对并及时unset,输出图片前禁用所有非图像输出,提升抗识别能力。

PHP 中用 $_SESSION 存验证码时,为什么总是验证失败?
最常见原因是会话未启动或跨请求丢失。PHP 验证码必须依赖会话存储服务端生成的值,但很多开发者漏掉 session_start(),或在验证码生成和表单提交两个脚本中没统一调用它。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 生成验证码图片的 PHP 脚本(如
captcha.php)开头必须有session_start() - 接收表单的脚本(如
login.php)同样要先执行session_start(),否则读不到$_SESSION['captcha_code'] - 确保两个脚本在同域、同 PHP 会话配置下运行;若用了
session_set_cookie_params()或自定义session.save_path,需保持一致 - 调试时可临时加
var_dump($_SESSION);确认键是否存在、大小写是否匹配('captcha_code'不是'Captcha_Code')
如何安全比对用户输入和 session 中的验证码?
不能直接用 == 或 === 做松散比较,尤其当验证码含数字 0 和字母 O、数字 1 和字母 l 时,大小写也得明确处理。更关键的是,要防时序攻击 —— 用 hash_equals() 替代普通字符串比较。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 生成验证码时统一转为小写并过滤易混淆字符(如去掉
O、I、0、l),存入$_SESSION['captcha_code']前用strtolower() - 用户提交后,同样对输入做
trim(strtolower($_POST['captcha']))再比对 - 务必使用
hash_equals($_SESSION['captcha_code'], $user_input),避免因字符串长度差异暴露服务端判断逻辑 - 比对完成后立即清空 session 中的验证码:
unset($_SESSION['captcha_code']),防止重放
imagepng() 输出验证码图后,为什么页面显示空白或报错?
本质是输出控制问题:验证码脚本必须只输出图片二进制流,不能有任何额外字符(包括空格、BOM、echo、var_dump、甚至文件末尾换行)。一旦输出了非图像内容,浏览器就无法解析。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 验证码脚本(如
captcha.php)开头不要有任何空行或 UTF-8 BOM,编辑器设为 “UTF-8 without BOM” - 确认没有
echo、print、var_dump等调试语句残留 - 设置正确 header:
header('Content-Type: image/png');必须在imagepng()之前,且不能有任何输出在它前面 - 若用框架或公共文件引入机制,确保没加载含输出的配置/工具文件;可把验证码逻辑单独拆成无依赖脚本
为什么验证码总被识别工具轻易破解?
不是代码写错了,而是生成策略太弱:纯随机字母 + 无干扰线 + 字体固定 + 无旋转扭曲 = OCR 工具几秒识别。后端验证再严也没用,因为攻击者根本不用人工输。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 放弃手写 GD 验证码,改用成熟库如
gregwar/captcha(Composer 安装),它默认带噪点、扭曲、多字体、背景干扰 - 若坚持自研,至少加入:随机字符倾斜角度(
imagettftext()的 angle 参数)、不规则字符间距、叠加半透明噪点点阵、背景渐变色 - 限制验证码有效期:存入 session 时同时记时间戳,验证前检查
time() - $_SESSION['captcha_time'] > 300(5 分钟) - 失败三次后强制刷新验证码,并考虑加简单 IP 限频(如
$_SERVER['REMOTE_ADDR']+ Redis 计数)
验证码最难的从来不是“怎么比对”,而是“怎么让机器难读、人还能认”。session 同步、输出控制、比对方式都是基础门槛,真正卡住项目上线的,往往是生成环节的抗识别能力——这点很容易被忽略,直到上线被扫库。



















