clicaptcha中文点击验证码开发经验总结
现在的验证码真是越来越高级了,12306 的找图验证码,极验的拖动式验证码,还有国外的一些黑科技,能智能判断你是不是机器人的验证码。
验证码的更新迭代让我突然对传统验证码一下子不满足了,出于挑战自我和对自己技能的修炼,我用了一周的时间写了一个简单的 demo ,然后又花了一周时间将其优化成插件的形式,于是 Clicaptcha 就诞生了。
简单介绍下 Clicaptcha ,它是由 click 和 captcha 这两个单子合并而成,顾名思义,这是一个点击验证码,那怎么个点击验证呢?整个操作流程只需根据提示文字信息,点击图中文字所在位置,即可完成验证,效果图下图:
具体的功能实现这里就不一步步给大家回顾了,感兴趣的可以直接上 oschina 或者 github ,搜索 Clicaptcha 就可以看到这个项目。
下面我主要是记录一下在开发过程中几个难点,以及我的解决思路,如果你有更好的,希望你能和我交流交流。
难点一:文字随机布局
首先我们要做一些准备工作:
- 背景图片
- 中文字体
- 随机文字
- 字体所占范围(因为是 php 生成,所以借助 GD 库里的 imagettfbbox 方法)
准备好这些后,就可以开始考虑我们的随机布局算法了,其实并不复杂,如果有看过我之前写的《绝对定位的层判断是否有相互覆盖的解决算法》,其实思路是差不多的。
可以看下上面这张图,假设中间带背景色的区域是已经固定的一个区域,当第2个区域要进行随机生成的时候,大概会有4种情况,也就是图中的这4种,我们只需依次判断以下4种条件,只要有一项符合,则这个随机生成的x,y坐标就可以使用。
x2 + w2 < x1
x1 + w1 < x2
y2 + h2 < y1
y1 + h1 < y2
难点二:字体大小有偏差
其实这倒不算个难点,就是个小细节。
GD 库里的 imagefttext 方法中,设置字体大小并不是以像素(px)为单位的,而是以磅(point)为单位。所以在具体使用的时候,需要进行转换,也就是乘以 0.75 ,比如你需要在图片上展示 50px 大小的字体,则需要 50px * 0.75 = 37.5point 。至于为什么是乘以 0.75 ,可以见下表:
八号 = 5磅(7px) = (5/72)*96 = 6.67 = 6px
七号 = 5.5磅 = (5.5/72)*96 = 7.3 = 7px
小六 = 6.5磅 = (6.5/72)*96 = 8.67 = 8px
六号 = 7.5磅 = (7.5/72)*96 = 10px
小五 = 9磅 = (9/72)*96 = 12px
五号 = 10.5磅 = (10.5/72)*96 = 14px
小四 = 12磅 = (12/72)*96 = 16px
四号 = 14磅 = (14/72)*96 = 18.67 = 18px
小三 = 15磅 = (15/72)*96 = 20px
三号 = 16磅 = (16/72)*96 = 21.3 = 21px
小二 = 18磅 = (18/72)*96 = 24px
二号 = 22磅 = (22/72)*96 = 29.3 = 29px
小一 = 24磅 = (24/72)*96 = 32px
一号 = 26磅 = (26/72)*96 = 34.67 = 34px
小初 = 36磅 = (36/72)*96 = 48px
初号 = 42磅 = (42/72)*96 = 56px
难点三:如何将图片和文字同时输出
我们都知道,在 PHP 中,通过设置 header 的参数,可以输出各种文件类型,但一次只能输出一种数据格式到客户端。
在我这个项目中,除了图片需要输出,同时还需要将提示文字也输出,不然用户就不知道依次点哪些文字进行验证了。
解决这个问题我想到有两种解决方案:
- 将图片保存,把图片地址和提示文字一并输出到前端
- 只输出图片到前端,同时将提示文件放入 cookie 中,前端调取 cookie 显示提示文字
最终我是选择了第二套方案,因为这是个验证码插件,如果每次生成的验证图片都保存下来,对服务器硬盘资源占用将是个大问题。
难点四:如何保证验证信息的安全
在我将后端代码全部开发完成,前端也封装好了一个 jQuery 插件后,发现了一个大问题,就是如果用户通过特殊手段跳过验证码验证,直接提交表单或者相关业务操作怎么办?
因为验证码是以插件的形式存在,所以在调用的参数里有一个 callback 参数,用于验证成功后执行网站本身业务逻辑的代码。这样就可能会有个问题,我用 chrome 按 F12 打开开发者工具,直接在任务台里输入了提交表单的代码并回车执行,然后表单顺利提交了,完完全全跳过了验证。
解决这个问题也不复杂,我思考了传统验证码的验证流程,核心一点就是它是随表单一起提交并做验证的,但由于我这个验证码的特殊性,所以只能增加一个后端二次验证,也就是前端初步验证后,将验证信息随表单提交到后端进行二次验证即可,同时,后端的二次验证成功后,将 session 清除,避免重复刷新提交表单造成能跳过二次验证的问题。
以上就是我对这个项目的难点总结,如果你看到这了,希望对感兴趣的你有点启发,这个项目我同时放在的 OSchina 和 Github 上,在线演示,有兴趣的可以关注下。
以下是针对前两个难点写的一个小demo,如果对完整的源码一时半会难理解的话,可以 copy 以下代码到本地,替换下字体和图片,然后运行一下看看效果。
header("Content-type: text/html; charset=utf-8");
error_reporting(E_ERROR | E_WARNING | E_PARSE); $imagePath = 'bg.jpg';
$fontPath = 'msyh.ttc';
//为什么要乘0.75?因为 imagefttext 方法里的 size 参数使用磅(point)做为单位的,所以需要进行转换,转换为像素
$fontSize = 50 * 0.75; //以“博客园”三个字举例,将文字、尺寸等信息存入数组
foreach(array('博', '客', '园') as $v){
$fontarea = imagettfbbox($fontSize, 0, $fontPath, $v);
$textWidth = $fontarea[2] - $fontarea[0];
$textHeight = $fontarea[1] - $fontarea[7];
$tmp['text'] = $v;
$tmp['size'] = $fontSize;
$tmp['width'] = $textWidth;
$tmp['height'] = $textHeight;
$textArr[] = $tmp;
} //获取背景底图宽高和类型信息
list($imageWidth, $imageHeight, $imageType) = getimagesize($imagePath); //随机生成汉字位置,并附加存入数组
foreach($textArr as &$v){
list($x, $y) = randPosition($textArr, $imageWidth, $imageHeight, $v['width'], $v['height']);
$v['x'] = $x;
$v['y'] = $y;
}
unset($v); //创建图片的实例
$image = imagecreatefromstring(file_get_contents($imagePath));
//字体颜色
$color = imagecolorallocate($image, 0, 0, 0);
//绘画文字
foreach($textArr as $v){
imagefttext($image, $v['size'], 0, $v['x'], $v['y'], $color, $fontPath, $v['text']);
}
//生成图片
switch($imageType){
case 1://GIF
header('Content-Type: image/gif');
imagegif($image);
break;
case 2://JPG
header('Content-Type: image/jpeg');
imagejpeg($image);
break;
case 3://PNG
header('Content-Type: image/png');
imagepng($image);
break;
default:
break;
}
imagedestroy($image); //随机生成位置布局
function randPosition($textArr, $imgW, $imgH, $fontW, $fontH){
$return = array();
$x = rand(0, $imgW - $fontW);
$y = rand($fontH, $imgH);
if(!checkPosition($textArr, $x, $y, $fontW, $fontH)){
$return = randPosition($textArr, $imgW, $imgH, $fontW, $fontH);
}else{
$return = array($x, $y);
}
return $return;
}
function checkPosition($textArr, $x, $y, $w, $h){
$flag = true;
foreach($textArr as $v){
if(isset($v['x']) && isset($v['y'])){
//分别判断X和Y是否都有交集,如果都有交集,则判断为覆盖
$flagX = true;
if($v['x'] > $x){
if($x + $w > $v['x']){
$flagX = false;
}
}else if($x > $v['x']){
if($v['x'] + $v['width'] > $x){
$flagX = false;
}
}else{
$flagX = false;
}
$flagY = true;
if($v['y'] > $y){
if($y + $h > $v['y']){
$flagY = false;
}
}else if($y > $v['y']){
if($v['y'] + $v['height'] > $y){
$flagY = false;
}
}else{
$flagY = false;
}
if(!$flagX && !$flagY){
$flag = false;
}
}
}
return $flag;
}
参考资料:
clicaptcha中文点击验证码开发经验总结的更多相关文章
- 在mvc中实现图片验证码的刷新
首先,在项目模型(Model)层中建立一个生成图片验证码的类ValidationCodeHelper,代码如下: public class ValidationCodeHelper { //用户存取验 ...
- 验证码在后台的编写,并实现点击验证码图片时时发生更新 C# 项目发布到IIS后不能用log4net写日志
验证码在后台的编写,并实现点击验证码图片时时发生更新 验证码在软件中的地位越来越重要,有效防止这种问题对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试:下面就是实现验证码的基本步骤: ...
- react项目中登陆注册验证码的倒计时,页面刷新不会重置
目前很多的网站和app在做登陆注册时都会用到手机验证码,为了防止验证码轰炸,也就是随意的点击验证码,一般我们需要对获取验证码进行一些限制,最常用到的是在规定时间内不得重复发送. 实现倒计时很简单,可以 ...
- a标签中有点击事件
我们常用的在a标签中有点击事件:1. a href="javascript:js_method();" 这是我们平台上常用的方法,但是这种方法在传递this等参数的时候很容易出问题 ...
- Selenium2学习-018-WebUI自动化实战实例-016-自动化脚本编写过程中的登录验证码问题
日常的 Web 网站开发的过程中,为提升登录安全或防止用户通过脚本进行黄牛操作(宇宙最贵铁皮天朝魔都的机动车牌照竞拍中),很多网站在登录的时候,添加了验证码验证,而且验证码的实现越来越复杂,对其进行脚 ...
- thinkphp3.2 代码生成并点击验证码
本人小菜鸟一仅仅.为了自我学习和交流PHP(jquery,linux,lamp,shell,javascript,server)等一系列的知识.小菜鸟创建了一个群.希望光临本博客的人能够进来交流.寻求 ...
- 我们常用的在a标签中有点击事件
我们常用的在a标签中有点击事件:1. a href="javascript:js_method();" 这是我们平台上常用的方法,但是这种方法在传递this等参数的时候很容易出问题 ...
- .NET中生成动态验证码
.NET中生成动态验证码 验证码是图片上写上几个字,然后对这几个字做特殊处理,如扭曲.旋转.修改文字位置,然后加入一些线条,或加入一些特殊效果,使这些在人类能正常识别的同时,机器却很难识别出来,以达到 ...
- Django中生成随机验证码(pillow模块的使用)
Django中生成随机验证码 1.html中a标签的设置 <img src="/get_validcode_img/" alt=""> 2.view ...
随机推荐
- javaweb回顾第十一篇过滤器(附实现中文乱码问题)
1:过滤器概念 过滤器就是一种在请求目标资源的中间组件,比喻把污水转换成纯净水中间需要一个污水净化设备,那么这个设备就好比一个过滤器.那么我用图来表示过滤器(可以有多个过滤器)运行的过程 2:Filt ...
- file:///Users/xmg/Desktop/xiangmu~Bsbdejie/BaisibudejieTheSecondtime/BaisibudejieTheSecond/BaisibudejieTheSecond/AppDelegate.m: warning: Missing file: /Users/xmg/Desktop/xiangmu~Bsbdejie/BaisibudejieT
warning: Missing file: is missing from working copy fatal error: file '-.h' has been modified since ...
- 【解决方案】安装vssdk_full.exe遇到的问题
Stop Block: CompatibilityMode : Windows Program Compatibility mode is on. Turn it off and then try S ...
- Request 接收参数乱码原理解析三:实例分析
通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...
- ArrayBuffer简析
关键技术: JavaScript,ArrayBuffer,Type Array,DataView,Web Worker,性能对比 ArrayBuffer 在文章开头列出了这些关键字,主要就是让大家了解 ...
- java加密解密的学习
注:此文章只是对如何学习java加密解密技术做一个讲解.并不涉及具体的知识介绍,如果有需要请留言,有时间我补冲长.个人觉着学习一个学习方法比学习一个知识点更有价值的多. 首先,对于加密解密知识体系没有 ...
- MVC中Action参数绑定的过程
一.题外话 上一篇:MVC中Action的执行过程 ControllerContext 封装有了与指定的 RouteBase 和 ControllerBase 实例匹配的 HTTP 请求的信息. 二. ...
- Android布局尺寸思考
一.初步思考 虽然安卓的这个显示适配的方案有点怪,最初也不容易马上理解,不过这个方案确实有其自己的道理,整个思路是清晰的,方案的也是完整的,没有硬伤 安卓采用的[屏幕密度放缩机制].与web前端对应的 ...
- 各大IT公司校园招聘程序猿笔试、面试题集锦
转自:http://blog.csdn.net/hackbuteer1/article/details/7959921#t4 百度一面 1.给定一个字符串比如“abcdef”,要求写个函数编程“def ...
- dp --- hdu 4939 : Stupid Tower Defense
Stupid Tower Defense Time Limit: 12000/6000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/ ...