在 laravel 中集成 mews/captcha 是最轻量且兼容性最好的图片验证码方案,支持 Laravel 9/10,基于 GD/Imagick 生成 base64 图片,仅将验证码字符串存入 session,需注意 GD 扩展启用、nginx 缓冲配置、session 驱动适配及正确校验方式。

如何在 Laravel 中集成 mews/captcha 生成图片验证码
直接用 mews/captcha 是目前 Laravel 项目里最轻量、兼容性最好的方案,Laravel 9/10 均可正常工作,无需改底层验证逻辑。它本质是基于 GD 或 Imagick 生成 base64 图片,不依赖 session 存储图片文件,只把验证码字符串存进 session。
安装命令:
composer require mews/captcha
安装后自动注册服务提供者(Laravel ≥5.5),若为旧版本需手动在 config/app.php 的 providers 数组中添加:
MewsCaptchaCaptchaServiceProvider::class
发布配置:
php artisan vendor:publish --provider="MewsCaptchaCaptchaServiceProvider"
会生成 config/captcha.php,里面可调整字体、尺寸、位数、过期时间等。默认是 4 位数字+字母混合,expire 单位是分钟,默认 60。
常见踩坑点:
-
gd扩展未启用会导致生成空白图或 500 错误,用php -m | grep gd确认 - 如果部署在 Nginx,需确保
fastcgi_buffering off或禁用 proxy buffering,否则 base64 图片可能被截断 - 使用 redis 驱动 session 时,
mews/captcha仍走默认 session 存储(即文件或 database),不会自动适配 Redis —— 它只读写session(),和你配置的 session driver 无关
如何在登录表单中嵌入验证码并提交校验
前端只需两处:一个显示图片的 标签,一个隐藏的 供用户填写。关键在于图片 URL 必须带随机参数防止浏览器缓存,例如:
@@##@@
后端校验不能只靠前端传来的 captcha 字段,必须调用 Captcha::check($request->captcha),它会从当前 session 里取出原始值比对,并自动清空已使用的验证码(防重放)。
典型登录控制器校验逻辑示例:
use MewsCaptchaFacadesCaptcha; // 在 login 方法中 if (! Captcha::check($request->captcha)) { return back()->withErrors(['captcha' => '验证码错误'])->withinput(); }
注意:Captcha::check() 返回布尔值,**不抛异常**;失败时 session 中的验证码值已被销毁,刷新页面后旧值不可再用。
容易忽略的细节:
- 务必在表单中保留
@csrf,否则 session 无法正常写入,导致Captcha::check()总是返回 false - 不要在验证规则里写
'captcha' => 'required|captcha'——mews/captcha并未注册这个验证规则,Laravel 会报Invalid validation rule - 如果用了多语言,验证码图片上的文字不会自动本地化,它是固定字符集(
0-9a-zA-Z),如需中文需换其他库(如gregwar/captcha+ 自定义字体)
如何避免验证码被绕过或暴力破解
单纯生成和校验只是第一步,真实场景中必须叠加防护策略。核心原则是:验证码不是独立安全机制,而是人机识别的第一道闸口,后面必须跟行为限制。
推荐组合措施:
- 登录失败 3 次后,强制要求输入验证码(即非必填变必填),通过
$request->session()->get('login_attempts', 0)计数 - 验证码提交失败后,立即刷新 session ID(
$request->session()->regenerate()),防止会话固定攻击 - 对同一 IP + 账号组合,15 分钟内最多允许 5 次验证码校验请求,超出则返回 429,可用 Laravel 的
throttle中间件配合自定义 key 实现 - 生产环境禁用
APP_DEBUG=true,否则Captcha::check()失败时可能泄露 session key 路径等调试信息
特别注意:mews/captcha 默认不记录日志,若需审计,应在 Captcha::check() 后手动写入日志,例如:
if (! Captcha::check($request->captcha)) { Log::warning('Captcha failed', ['ip' => $request->ip(), 'user_input' => $request->captcha]); }
为什么不用 Laravel 自带的 VerifyCsrfToken 或自定义中间件替代验证码
CSRF Token 解决的是跨站请求伪造,不是人机识别。即使加了 @csrf,自动化脚本仍可先 GET 登录页提取 token,再 POST 提交 —— 这正是验证码要拦住的行为。
自定义中间件做频率限制(如每分钟最多 3 次登录请求)有用,但无法区分真人反复输错和机器穷举。验证码的价值在于引入「视觉识别」这一人类天然优势环节。
真正容易被忽视的点是:验证码必须与账号锁定策略联动。比如某手机号连续 5 次验证码错误,就应临时冻结该账号 30 分钟,而不是仅提示“验证码错误”。这个逻辑不会由 mews/captcha 自动完成,得你手写。