资讯动态

PHP拼图滑块验证码源码实现:GD库抠图、坐标存储与防作弊校验

发布时间:2026/9/9 17:03:59 来源:尧图企业网站定制
简介这套拼图验证码源码包面向需要为网站表单接入人机校验功能的PHP开发者资源定位清晰可直接用于用户登录、注册、评论留言、活动抽奖等常见交互场景。实现方案以随机切割背景图为起点将若干个图像碎片利用base64编码转换为字符串便于网络传输前端通过JavaScript完成拼块拖拽与顺序调整后端则使用session保存正确排序并校验用户提交结果代码结构明确方便二次开发与学习。压缩包共6个文件主体为1个PHP主文件和4张示例PNG图片另含1个macOS系统自动生成的DS_Store文件可忽略整个包仅133KB十分轻量。源码中覆盖图像切割、坐标随机化、安全防重放、前后端联调等关键细节并附带可运行的拼图演示页面开发者可以对照代码直观理解拼图验证码从图片处理、前端交互到后端验证的完整工作流程也能快速提取核心逻辑集成到自己的项目中。目前已有501人学习/下载适合有一定PHP基础、希望掌握验证码实现原理或需要轻量级且可靠人机校验方案的技术人员。 我最早做拼图验证码是因为一个老项目被刷得实在没法看。用户注册接口被脚本批量调用一夜之间多了几千个僵尸账号后台列表拉不到底。后来换成字符验证码结果又被接入了打码平台识别率比我肉眼还高。被逼到这份上才决定自己写一套拼图滑块验证码的PHP源码。折腾下来发现这东西远没有想象中那么玄乎核心就三块生成拼图、前端交互、后端校验。每一块都有不少细节踩过的坑也值得记一笔。这篇文章就把我完整实现的思路和代码逻辑拆开讲清楚包括GD库怎么抠图、缺口坐标怎么存、前端怎么画图、后端怎么校验、防作弊怎么设容差、部署到服务器上会遇到哪些问题适合正准备自己实现验证码或者想搞清楚拼图验证码原理的PHP开发者参考。1. 为什么拼图验证码越来越常见从交互体验到后端校验1.1 传统验证码的困境很多人对验证码的印象还停留在“输入四位数字”“拖动滑块拼到最右边”这些老样式上。实际上字符验证码的识别成本已经被打到极低。过去那些扭曲的字母数字现在用现成的OCR模型几秒钟就能解出来。再加上打码平台按量计价破解传统验证码的成本降到了几分钱一次基本等于没有防护。滑块到最右这种玩法早被脚本用模拟轨迹直接秒过纯属自欺欺人。拼图验证码之所以流行是因为它把攻击门槛从“识别字符”提高到了“图像理解行为模拟”两层。攻击者要过你这一关得先知道你缺口在哪儿还得模拟出像人的拖动轨迹。这两件事单独看都不难但合在一起配合好服务端策略能把脚本成本拉高一大截。1.2 拼图验证码的核心交互流程拼图验证码的完整链路说白了就是一次“挑战-应答”过程。用户打开页面时前端向后端请求一张验证码图片后端随机生成一张背景图和一个缺口位置然后把带缺口的背景图和一个小拼图块交给前端。用户需要把拼图块拖到缺口正中央。拖完后前端把最终坐标和拖动轨迹发给后端后端对比坐标是否在允许误差范围内同时判断轨迹是不是像人拖的。这里最关键的一点是缺口坐标必须由服务端生成并保存在服务端。如果你把缺口坐标直接跟着图片接口返回给前端那就等于把答案亲手递给别人。脚本连识别都不用做直接解析接口响应里的坐标就能过。1.3 为什么选择自己写一套PHP源码我见过不少团队直接买第三方验证码服务或者用现成的开源组件。买服务的缺点是费用和依赖验证码服务挂了你的登录注册也跟着挂而且用户数据流向第三方多少有点别扭。现成开源组件通常是很重的前后端一体包想改交互细节或者接入现有用户体系反而费劲。自己写PHP版的好处是轻量、可控、易懂单文件就能跑通方便根据业务情况自己加防御策略。这篇源码也不是什么大工程核心就几个函数的事。2. 核心算法拆解抠图、缺口定位与容差设计2.1 服务端生成拼图任务的完整步骤我用的PHP版本是7.4以上核心依赖是GD库扩展。生成拼图任务分五步走从预设的背景图目录里随机选一张图。在图上随机选一个点作为缺口中心位置注意X轴要留出足够边距不然拼图块会超出图片边界。用GD库把缺口位置的图像抠出来生成一张带锯齿边缘或圆角的小拼图块。在背景图上把缺口区域覆盖成高亮色或者加深色让用户肉眼能看出哪儿缺了一块。把拼图块的坐标只存X和Y写入Session再把背景图和拼图块一起返回给前端。核心代码大概长这样public function generate(Request $request) { $bg $this-getRandomBg(); // 随机背景图路径 $bgImg imagecreatefromjpeg($bg); $bgWidth imagesx($bgImg); $bgHeight imagesy($bgImg); // 拼图块尺寸这里用 50*50 $puzzleSize 50; // 缺口X范围留20像素边距Y范围也要留边距 $x mt_rand(20 $puzzleSize, $bgWidth - $puzzleSize - 20); $y mt_rand(20 $puzzleSize, $bgHeight - $puzzleSize - 20); // 创建拼图块透明底 $puzzle imagecreatetruecolor($puzzleSize, $puzzleSize); imagealphablending($puzzle, false); imagesavealpha($puzzle, true); $transparent imagecolorallocatealpha($puzzle, 0, 0, 0, 127); imagefill($puzzle, 0, 0, $transparent); // 从背景图上复制一块到拼图块 imagecopy($puzzle, $bgImg, 0, 0, $x, $y, $puzzleSize, $puzzleSize); // 背景图上缺口区域填充阴影 $shade imagecolorallocatealpha($bgImg, 0, 0, 0, 60); imagefilledrectangle($bgImg, $x, $y, $x $puzzleSize, $y $puzzleSize, $shade); // 输出到内存 ob_start(); imagejpeg($bgImg, null, 90); $bgData ob_get_clean(); ob_start(); imagepng($puzzle); $puzzleData ob_get_clean(); // 坐标存session session_start(); $_SESSION[captcha_x] $x; $_SESSION[captcha_y] $y; return response()-json([ bg data:image/jpeg;base64, . base64_encode($bgData), puzzle data:image/png;base64, . base64_encode($puzzleData), ]); }注意这里我用mt_rand而不是randPHP 7.1之后rand别名已经是mt_rand了但老代码里有时候还混着用统一写mt_rand更规范。2.2 缺口边缘的锯齿效果与透明度处理上面是最朴素的做法方形拼图块。但方形块太容易被图像识别算法检测到边缘尤其在底部和右侧矩形轮廓非常明显。为了增加机器识别难度我会给拼图块加一道“齿轮”轮廓。实现思路也不复杂在拼图块四周的像素遍历中对边界像素做透明度扰动。比如边缘一圈随机让部分像素的Alpha变成30、60、100造成毛刺感。更简单的做法是画几个小圆形凸起// 在拼图块边缘加半圆凸起 $radius 8; for ($angle 0; $angle 360; $angle 15) { $cx $x $puzzleSize / 2 cos(deg2rad($angle)) * ($puzzleSize / 2 - 2); $cy $y $puzzleSize / 2 sin(deg2rad($angle)) * ($puzzleSize / 2 - 2); imagefilledellipse($puzzle, $cx - $x, $cy - $y, $radius, $radius, $transparent); }这段代码的意思是以拼图块中心为圆心在靠近边缘的位置画一圈小圆形然后把圆形内部设为透明。这样生成出来的拼图块轮廓不是规整矩形轮廓提取的难度会高一些。实际跑下来用户体验几乎无感但图像识别脚本想精准框出拼图块的位置就要多费点功夫。2.3 坐标存储与会话隔离很多人会忽略一个细节Session存储坐标时必须考虑到用户并发请求的情况。比如用户打开页面同一时间发起两个验证码请求后一个请求会把前一个的坐标覆盖掉。前端拿到的图片和后端校验的坐标对不上用户再怎么拖都过不了。我的解决方式是每次请求都生成一个captcha_id可以用uniqid或session_create_id前端拿到的图片会把captcha_id跟随接口返回拖动完成提交时带上这个ID。服务端用这个ID定位坐标而不是用Session里固定字段。这样即使并发生成多张验证码也不会串号。$captchaId md5(uniqid(captcha_, true)); $_SESSION[captcha_ . $captchaId] [ x $x, y $y, expire time() 120, ];校验时从$_SESSION[captcha_ . $captchaId]里取坐标比对完就删掉这个session键保证一次性有效。2.4 误差容差设置的依据拼图拖过去不可能像素级对齐用户手一抖差一两个像素很正常。容差设太大脚本随便蒙都能过设太小真人用户会疯狂失败然后流失。我实测下来50像素的拼图块X轴容差5到8像素比较合适。Y轴呢因为拼图块没有上下滑动需求Y坐标应该是固定的拼图块唯一位置就是缺口处所以Y轴只能精准匹配但前端传Y也要校验一下是否在给定范围内。具体对比逻辑举个例子$diffX abs($inputX - $sessionData[x]); $diffY abs($inputY - $sessionData[y]); $pass $diffX 6 $diffY 6;这个6像素是怎么定出来的我跑了一批测试让20个真人用户各操作10次统计拖动误差分布。绝大多数人的最终误差在0到4像素之间偶尔飘到5、6像素。设成8像素合适但如果风控要求更严也可以动态调整——比如同一IP连续多次验证失败时把容差从8缩到4这时候真人也会容易失败但攻击者更难受。3. 前端滑块交互与后端校验的接口约定3.1 canvas绘制与拖动逻辑前端我用原生JavaScript canvas实现。拿到后端返回的背景图和拼图块base64后先把背景图画到canvas再把拼图块画到背景图上方初始位置可以随机放在X轴左侧某个位置。用户点住拼图块拖动时实时更新它的X坐标形成一个“跟手”的效果。核心代码const bgImg new Image(); const puzzleImg new Image(); bgImg.onload function() { ctx.drawImage(bgImg, 0, 0, canvas.width, canvas.height); drawPuzzle(); }; bgImg.src data.bg; puzzleImg.src data.puzzle; let dragStartX 0; let currentX 0; slider.addEventListener(mousedown, function(e) { dragStartX e.clientX; document.addEventListener(mousemove, onMove); }); function onMove(e) { const offset e.clientX - dragStartX; currentX Math.min(Math.max(0, offset), maxDragWidth); drawPuzzle(); } function drawPuzzle() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(bgImg, 0, 0, canvas.width, canvas.height); ctx.drawImage(puzzleImg, currentX startX, puzzleY, puzzleSize, puzzleSize); }注意canvas的分辨率要跟后端生成时的尺寸一致。如果你后端生成图是600x300前端canvas就不能设成500x300否则坐标系统对不上容差再大也得失败。这块最容易出bug很多套用别人代码没跑通的案例都是尺寸不一致导致的。3.2 拖动轨迹的采集与判断只校验最终坐标太容易被模拟脚本直接提交最终位置坐标就能过。所以我同时记录了拖动过程中的所有坐标点计算轨迹特征拖动总时长毫秒轨迹点数量是否有回退往回拖拖动过程中是否有停顿真人拖动通常不是一条直线匀速到达的会有一个加速-减速的过程偶尔微调回退总时长在300到1500毫秒之间。简单的脚本模拟往往是直线匀速或者轨迹点太少比如只有起点终点两个点。后端可以针对这些特征做简单规则判断。采集前端轨迹的代码let trace []; function onMove(e) { trace.push({ x: currentX, t: Date.now() }); } // 提交时 fetch(/api/captcha/verify, { method: POST, body: JSON.stringify({ captcha_id: data.captcha_id, x: currentX startX, // 最终X坐标 y: puzzleY, // Y坐标 trace: trace }) });注意我把startX加了进去因为前端canvas里拼图块还有一个初始的随机偏移量真正提交的坐标要换成相对于整张背景图的坐标服务端拿到的才是绝对值。3.3 后端校验三重关卡后端校验不能只比对坐标要做三道检查坐标误差检查和Session中存的缺口坐标比看是否在容差范围内。轨迹合法性检查轨迹点的持续时间、距离变化是否像人类操作。一次性校验验证成功后立即删除session记录防止重放攻击。三道全过才算通过。这里我从不用“绝对信任前端传上来的坐标”的策略因为F12一改数值就能伪造。坐标只是参考轨迹特征才是辅助证据真正的保险是session里那份只能读不能改的答案。4. 部署到宝塔环境时踩过的几个真坑4.1 GD库未安装导致的blank图我第一次把这套代码部署到宝塔环境结果接口返回的base64是空的前端图片画不出来。排查半天发现是PHP的GD扩展没装。宝塔默认的PHP环境不一定带GD需要在软件管理里找到对应PHP版本点“安装扩展”勾上GD库装完重启PHP-FPM。验证GD有没有启用最好在命令行跑一下php -m | grep gd如果有输出gd说明装好了。注意装完要重启PHP-FPM否则不生效。4.2 track_errors被移除的兼容性错误在PHP 8上跑老代码时可能遇到一个报错Fatal error: Directive track_errors is no longer available in PHP这是php.ini里默认配置了track_errors On或者代码里用ini_set设置过PHP 8.0把这个指令彻底移除了。解决方法就是删掉php.ini里相关配置或者把代码里的ini_set(track_errors, 1)删掉。这个跟验证码逻辑本身无关但如果项目里还有老代码在跑升级PHP版本很容易撞上。4.3 跨域导致验证码加载失败前端如果部署在别的域名下接口跨域请求就来了。尤其是用base64图片时如果后端没设置跨域头浏览器会直接拦截验证码压根加载不出来。我的处理方式是在后端统一加跨域响应头header(Access-Control-Allow-Origin: . $allowOrigin); header(Access-Control-Allow-Methods: GET, POST, OPTIONS); header(Access-Control-Allow-Headers: Content-Type);在宝塔面板里也可以直接配置Nginx跨域头但这块容易跟后端重复加导致冲突建议二选一。我更推荐后端加因为可以动态判断来源域名更灵活。4.4 Session并发写入的坑一个容易踩的坑是PHP的Session文件锁机制同一用户并发请求时后一个请求会阻塞等待前一个释放锁。如果验证码请求和验证接口是分开的两个请求理论上没问题。但如果你在生成验证码的接口里先调了session_start之后又调用了其他需要session的接口可能排队时间会变长。一旦用户前端图片加载慢验证码超时了再点验证就会提示过期。解决办法是生成接口里用完session立刻调用session_write_close()释放锁后续逻辑不依赖session就可以先关掉。5. 安全性加固的最后几道工序5.1 防止反向查询坐标有人会问既然坐标存在session里那脚本直接请求生成接口然后读取session值不就拿到答案了对前提是他能拿到你的session。这依赖于会话固定、XSS窃取Cookie等方式。所以在使用拼图验证码的同时必须确保整个站点的会话安全HttpOnly Cookie、SameSite属性、防XSS注入。否则再好的验证码也白搭。另外不要在前端返回任何跟坐标有关的信息。我见过有人把缺口坐标放在图片的alt属性里或者用CSS定位标出来这等于给脚本发福利。前端只能拿到图片本身坐标的感知只能靠用户的“眼睛”。5.2 验证码有效期与失败次数限制有效期我设成了120秒超时自动作废。这个值可以按业务调验证码生成后用户拖半天拖不出来真着急但脚本暴力试错更着急。设一个合理有效期配合失败次数限制比如5次失败就要求刷新能有效减少穷举。注意每次失败最好不要直接删掉session记录而是记录失败次数。否则脚本重放旧请求时会导致合法用户也被迫刷新。5.3 更进一步的拓展方向这套基础版做完了实际生产环境还可以继续强化。比如增加背景图混淆在背景上叠加噪点、细线干扰图像识别模型。或者加字体水印让缺口区域附近的纹理更复杂提高目标检测的难度。也可以把坐标存Redis而不是Session这样可以支持分布式部署多个PHP实例共享同一份验证码数据。升级方向不少先把基础版跑稳再按实际攻击情况逐步加固。5.4 性能与并发考量base64图片数据比较大每张验证码大概几十KB如果接口被高频请求PHP-FPM的CPU和内存消耗会上涨。建议在Nginx层加缓存同一张背景图生成后缓存一段时间减少重复创建图片的资源消耗。另外图片接口建议走CDN或者不加业务的Cookie能减少带宽压力。不过拼图验证码的场景通常是登录注册等低频接口并发压力不大基础版本够用。我自己把这套代码跑了大半年拦截率明显上去了误伤率也控制得住。实际使用中最大的体会是验证码这东西没有绝对的安全只有让攻击成本大于收益。一个拼图验证码也只是整个风控体系里的一环配合行为检测、频率限制、IP信誉分才能发挥最大作用。希望这套PHP实现能帮你省下踩坑的时间让你把精力花在真正的业务上。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价