手写验证码需三步:生成带干扰的图片、将验证码存入$_SESSION、提交后比对;关键要确保session_start()在所有输出前调用,GD扩展启用,且验证码一次性有效。

PHP 表单里怎么加验证码输入框(不用插件)
直接手写验证码比套插件更可控,也避免引入不维护的第三方代码。核心是三件事:生成带干扰的图片、把验证码字符串存进 $_SESSION、表单提交后比对用户输入和 session 里的值。
关键点:session_start() 必须在任何输出前调用;GD 扩展要启用(检查 phpinfo() 里有没有 gd);验证码图片建议设为一次性有效(验证后立即 unset($_SESSION['captcha_code']))。
一个极简示例:
/* captcha.php */
session_start();
$code = substr(str_shuffle('23456789ABCDEFGHJKLMNPQRSTUVWXYZ'), 0, 4);
$_SESSION['captcha_code'] = strtolower($code);
header('Content-Type: image/png');
$im = imagecreatetruecolor(100, 30);
$bg = imagecolorallocate($im, 240, 240, 240);
imagefilledrectangle($im, 0, 0, 99, 29, $bg);
$text_color = imagecolorallocate($im, 80, 80, 80);
imagestring($im, 5, 20, 8, $code, $text_color);
imagepng($im);
imagedestroy($im);
然后在表单页嵌入:,点击刷新靠加随机参数防缓存。
立即学习“PHP免费学习笔记(深入)”;
用 PHPMailer 或 Formidable 这类插件加验证码会出什么问题
多数“表单插件”本身不带验证码逻辑,只是帮你发邮件或存数据库。强行塞进去容易踩坑:
- 插件没暴露钩子(hook),你没法在验证前拦截提交
- 验证码校验逻辑被写死在插件内部,升级后覆盖掉
- 插件用
$_POST直接取值,但你验证码存在$_SESSION,类型/键名不匹配就始终报错 - 有些插件默认开启 CSRF token,和你自己的验证码 session 冲突,导致“明明输对了还说错误”
真要用插件,优先选支持自定义验证回调的(比如 add_filter('form_validate', 'my_captcha_check') 这类接口),而不是靠改插件源码。
$_SESSION['captcha_code'] 总是为空或校验失败
这是最常遇到的问题,原因很具体:
-
session_start()没在captcha.php和表单处理页(如process.php)都调用,或者调用位置错了(比如在echo之后) - 验证码图片请求(
captcha.php)和表单提交是两个独立 HTTP 请求,必须共用同一个 session_id —— 如果用了 iframe 或跨域加载图片,session 就断了 - 用户开了无痕模式,或浏览器禁用了 cookie,
session无法维持 - 比对时大小写没统一:
strtolower($_POST['captcha']) !== $_SESSION['captcha_code'],而 session 里存的是小写,用户输的是大写 - PHP 的
session.gc_maxlifetime设得太短(比如 30 秒),用户还没填完表单,验证码就过期了
要不要用 reCAPTCHA v3 替代自建验证码
如果表单面向公网且有刷量风险,reCAPTCHA v3 更省事;但它不是“输入框”,而是后台打分,需要你配合前端 grecaptcha.execute() 和后端调 https://www.google.com/recaptcha/api/siteverify 验证 token。
注意几个现实约束:
- 国内访问 Google 域名不稳定,
file_get_contents或cURL可能超时,得加重试和 fallback(比如降级为简单算术题) - v3 不显示 UI,用户无感知,但某些合规场景(比如注册页)明确要求“用户主动操作”,这时必须用 v2 的 checkbox 或 invisible badge
- 你得在
g-recaptcha-response字段里取值,别错写成g-recaptcha或漏掉name属性
真正难的不是集成步骤,而是当 score










