clicaptcha中文点击验证码开发经验总结

  现在的验证码真是越来越高级了,12306 的找图验证码,极验的拖动式验证码,还有国外的一些黑科技,能智能判断你是不是机器人的验证码。

  验证码的更新迭代让我突然对传统验证码一下子不满足了,出于挑战自我和对自己技能的修炼,我用了一周的时间写了一个简单的 demo ,然后又花了一周时间将其优化成插件的形式,于是 Clicaptcha 就诞生了。

  简单介绍下 Clicaptcha ,它是由 click 和 captcha 这两个单子合并而成,顾名思义,这是一个点击验证码,那怎么个点击验证呢?整个操作流程只需根据提示文字信息,点击图中文字所在位置,即可完成验证,效果图下图:

  具体的功能实现这里就不一步步给大家回顾了,感兴趣的可以直接上 oschina 或者 github ,搜索 Clicaptcha 就可以看到这个项目。

  下面我主要是记录一下在开发过程中几个难点,以及我的解决思路,如果你有更好的,希望你能和我交流交流。

  难点一:文字随机布局

  首先我们要做一些准备工作:

  1. 背景图片
  2. 中文字体
  3. 随机文字
  4. 字体所占范围(因为是 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 的参数,可以输出各种文件类型,但一次只能输出一种数据格式到客户端。

  在我这个项目中,除了图片需要输出,同时还需要将提示文字也输出,不然用户就不知道依次点哪些文字进行验证了。

  解决这个问题我想到有两种解决方案:

  1. 将图片保存,把图片地址和提示文字一并输出到前端
  2. 只输出图片到前端,同时将提示文件放入 cookie 中,前端调取 cookie 显示提示文字

  最终我是选择了第二套方案,因为这是个验证码插件,如果每次生成的验证图片都保存下来,对服务器硬盘资源占用将是个大问题。

  难点四:如何保证验证信息的安全

  在我将后端代码全部开发完成,前端也封装好了一个 jQuery 插件后,发现了一个大问题,就是如果用户通过特殊手段跳过验证码验证,直接提交表单或者相关业务操作怎么办?

  因为验证码是以插件的形式存在,所以在调用的参数里有一个 callback 参数,用于验证成功后执行网站本身业务逻辑的代码。这样就可能会有个问题,我用 chrome 按 F12 打开开发者工具,直接在任务台里输入了提交表单的代码并回车执行,然后表单顺利提交了,完完全全跳过了验证。

  解决这个问题也不复杂,我思考了传统验证码的验证流程,核心一点就是它是随表单一起提交并做验证的,但由于我这个验证码的特殊性,所以只能增加一个后端二次验证,也就是前端初步验证后,将验证信息随表单提交到后端进行二次验证即可,同时,后端的二次验证成功后,将 session 清除,避免重复刷新提交表单造成能跳过二次验证的问题。

  以上就是我对这个项目的难点总结,如果你看到这了,希望对感兴趣的你有点启发,这个项目我同时放在的 OSchinaGithub 上,在线演示,有兴趣的可以关注下。

  以下是针对前两个难点写的一个小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;
}

  参考资料:

1、绝对详解PHP的imageTtfText()函数

2、imagettfbbox()

3、imagettftext()

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏目标检测和深度学习

谷歌GPU资源免费啦

注:本文编译自medium,原英文链接:https://medium.com/@nickbourdakos/train-your-machine-learnin...

42970
来自专栏机器之心

教程 | 如何使用Swift在iOS 11中加入原生机器学习视觉模型

选自Hackernoon 机器之心编译 作者:Alex Wulff 参与:侯韵楚、李泽南 随着 WWDC 大会上 iOS 11 的发布,苹果终于推出了原生机器学...

32350
来自专栏CDA数据分析师

如何在Python中用Bokeh实现交互式数据可视化?

引言 最近,我一直在看美国德克萨斯州奥斯汀举办的SciPy 2015会议上的一段视频——“用Blaze和Bokeh创建Python数据应用程序”,并且情不自禁地...

40170
来自专栏机器人网

PID控制原理:看完这三个故事,你就明白了

一、PID的故事 小明接到这样一个任务:有一个水缸点漏水(而且漏水的速度还不一定固定不变),要求水面高度维持在某个位置,一旦发现水面高度低于要求位置,就要往水缸...

1.1K30
来自专栏智能算法

TensorFlow Lite在Kika Keyboard中的应用案例分享

『基于 AI 技术变革沟通,让世界沟通更简单』一直是 Kika keyboard 最重要的使命。从2016年开始,Kika 技术团队一直致力于 AI 技术在移动...

23840
来自专栏大数据文摘

手把手 | 如何在你的iPhone上建立第一个机器学习模型(Apple最新CoreML框架入门)

39250
来自专栏机器人网

PID控制原理:看完这个故事你就明白了

小明接到这样一个任务:有一个水缸漏水,且漏水的速度是不定的,但要求水面高度维持在某个位置,一旦发现水面高度低于要求位置,就要往水缸里加水。 ? 开始小明用瓢加水...

35750
来自专栏点滴积累

geotrellis使用(二十七)栅格数据色彩渲染

目录 前言 复杂原因及思路分析 实现过程 总结 一、前言        今天我们来研究一下这个看似简单的问题,在地理信息系统中颜色渲染应当是最基本的操作和功能,...

42550
来自专栏大数据文摘

交互式数据可视化,在Python中用Bokeh实现

445110
来自专栏灯塔大数据

分析 | Python抓取婚恋网用户数据,原来这才是年轻人的择偶观

刚好在看决策树这一章,书里面的理论和例子让我觉得这个理论和选择对象简直不能再贴切。看完长相看学历,看完学历看收入。

26430

扫码关注云+社区

领取腾讯云代金券