网站验证码的原理及PHP实现
在网站开发中,为了防止有人恶意频繁调用我们的接口,经常会用到验证码,比如防止有人通过获取发送短信的接口恶意刷短信,或者防止密码的暴力破解等等,今天小编花了一天时间研究了一下验证码的原理并用PHP实现,给大家分享一下。
我们用到最多的就是字符图片验证码(输入图片中的字符),运算验证码(图片显示一个数学计算题,用户输入计算结果),滑动拼图,图片点选等等,后面这两个是基于用户操作行为的,太难了,因此小编这次没有实现,主要是实现了字符图片验证码和运算验证码。
前台请求一张图片验证码,后台随即生成不同字符组合的图片返回给前端,并通过session保存验证码的数值;
当用户提交数据时,通过比对表单中用户输入的验证码和session中保存的验证码是否一致,来判断输入是否正确
主要用到PHP的图片绘制功能,生成一个验证码图片我们分这几步骤进行
生成一个随即字符
绘制图片区域及背景
绘制第一步生成的随即字符
添加一些干扰线或者噪点
输入图片
在session中保存验证码
先上代码,以下为PHP YII框架下代码,可以根据情况自行修改
namespace common\helpers; class ValidateCode{ private $charset='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';//随即字符库 private $code;//验证码 private $codelen=4;//验证码长度 private $width=130;//宽度 private $height=50;//高度 private $img;//图形资源句柄 private $font;//指定的字体 private $fontsize=20;//指定字体大小 private $fontcolor;//指定字体颜色 private $linecount=3;//横线数量 private $snowcount=20;//雪花数量 private $addcode;//加法运算要绘制的字符 public function __construct($config){ $this->font = dirname(dirname(__DIR__)).'/frontend/web/font/11.ttf';//注意字体路径要写对,否则显示不了图片 if(isset($config['charset'])){ $this->charset=$config['charset']; } if(isset($config['codelen'])){ $this->codelen=$config['codelen']; } if(isset($config['width'])){ $this->width=$config['width']; } if(isset($config['height'])){ $this->height=$config['height']; } if(isset($config['fontsize'])){ $this->fontsize=$config['fontsize']; } if(isset($config['linecount'])){ $this->linecount=$config['linecount']; } if(isset($config['snowcount'])){ $this->snowcount=$config['snowcount']; } } // 生成随即验证码 private function createCode(){ $len=strlen($this->charset)-1; $this->code=''; for($i=0;$i<$this->codelen;$i++){ $this->code.=$this->charset[mt_rand(0,$len)]; } } //加法运算,字符放入addcode,结果放入code private function createAddCode(){ $first=mt_rand(0,9); $second=mt_rand(0,9); $this->addcode=$first.'+'.$second.'='; $this->code=$first+$second; } //生成背景 private function createBg(){ $this->img=imagecreatetruecolor($this->width,$this->height); $color=imagecolorallocate($this->img,mt_rand(157,255),mt_rand(157,255),mt_rand(157,255)); imagefilledrectangle($this->img,0,0,$this->width,$this->height,$color); } //生成白色背景 private function createWhiteBg(){ $this->img=imagecreatetruecolor($this->width,$this->height); $color=imagecolorallocate($this->img,255,255,255); imagefilledrectangle($this->img,0,0,$this->width,$this->height,$color); } //生成文字 private function createFont(){ $_x=($this->width-10)/$this->codelen; for($i=0;$i<$this->codelen;$i++){ $this->fontcolor=imagecolorallocate($this->img,mt_rand(0,200),mt_rand(0,200),mt_rand(0,200)); imagettftext($this->img,$this->fontsize,mt_rand(-30,30),$_x*$i+10+mt_rand(-5,15),$this->height / 1.4,$this->fontcolor,$this->font,$this->code[$i]); } } //生成加法字符 private function createAddFont(){ $_x=($this->width-10)/4; for($i=0;$i<4;$i++){ $this->fontcolor=imagecolorallocate($this->img,mt_rand(0,200),mt_rand(0,200),mt_rand(0,200)); imagettftext($this->img,$this->fontsize,0,$_x*$i+10+mt_rand(-5,15),$this->height / 1.4,$this->fontcolor,$this->font,$this->addcode[$i]); } } //生成线条、雪花 private function createLine() { //线条 for ($i=0;$i<$this->linecount;$i++) { $color = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156)); imageline($this->img,mt_rand(0,$this->width),mt_rand(0,$this->height),mt_rand(0,$this->width),mt_rand(0,$this->height),$color); } //雪花 for ($i=0;$i<$this->snowcount;$i++) { $color = imagecolorallocate($this->img,mt_rand(200,255),mt_rand(200,255),mt_rand(200,255)); imagestring($this->img,mt_rand(1,5),mt_rand(0,$this->width),mt_rand(0,$this->height),'*',$color); } } //输出 private function outPut() { header('Content-type:image/png'); imagepng($this->img); imagedestroy($this->img); } //对外生成随即字符验证码 public function doimg() { $this->createBg(); $this->createCode(); $this->createLine(); $this->createFont(); $this->outPut(); } //对外生成加法运算验证码 public function doimgAdd() { $this->createBg(); $this->createAddCode(); $this->createLine(); $this->createAddFont(); $this->outPut(); } //对外生成白色背景验证码 public function doimgWhiteBg() { $this->createWhiteBg(); $this->createCode(); $this->createLine(); $this->createFont(); $this->outPut(); } //对外生成加法运算验证码 public function doimgAddWhiteBg() { $this->createWhiteBg(); $this->createAddCode(); $this->createLine(); $this->createAddFont(); $this->outPut(); } //获取验证码 public function getCode() { return strtolower($this->code); } }
第一步:生成随即字符串
从我们提供的字符串库(小写字母,大写字母,数字)中生成一定数量的字符串
private function createCode(){ $len=strlen($this->charset)-1; $this->code=''; for($i=0;$i<$this->codelen;$i++){ $this->code.=$this->charset[mt_rand(0,$len)]; } }
第二步:绘制背景
绘制一个图片背景颜色随即的背景,如果想要绘制白色背景,就把3个随机数改为255
private function createBg(){ $this->img=imagecreatetruecolor($this->width,$this->height); $color=imagecolorallocate($this->img,mt_rand(157,255),mt_rand(157,255),mt_rand(157,255)); imagefilledrectangle($this->img,0,0,$this->width,$this->height,$color); }
第三步:绘制字符
这里注意,需要提前设置字体ttf文件的路径
private function createFont(){ $_x=($this->width-10)/$this->codelen; for($i=0;$i<$this->codelen;$i++){ $this->fontcolor=imagecolorallocate($this->img,mt_rand(0,200),mt_rand(0,200),mt_rand(0,200)); imagettftext($this->img,$this->fontsize,mt_rand(-30,30),$_x*$i+10+mt_rand(-5,15),$this->height / 1.4,$this->fontcolor,$this->font,$this->code[$i]); } }
第四步:加入干扰因素,增加破解难度
验证码干扰太多了,会影响用户的输入,总不能让别人输入好几次才能搞对一次,那样的验证码本末倒置了,我们应该在保护良好用户体验的情况下,保证网站安全,不应该通过过度为难用户来提升安全
private function createLine() { //线条 for ($i=0;$i<$this->linecount;$i++) { $color = imagecolorallocate($this->img,mt_rand(0,156),mt_rand(0,156),mt_rand(0,156)); imageline($this->img,mt_rand(0,$this->width),mt_rand(0,$this->height),mt_rand(0,$this->width),mt_rand(0,$this->height),$color); } //雪花 for ($i=0;$i<$this->snowcount;$i++) { $color = imagecolorallocate($this->img,mt_rand(200,255),mt_rand(200,255),mt_rand(200,255)); imagestring($this->img,mt_rand(1,5),mt_rand(0,$this->width),mt_rand(0,$this->height),'*',$color); } }
第5步:输出图片
private function outPut() { header('Content-type:image/png'); imagepng($this->img); imagedestroy($this->img); }
在控制器文件中,我们这么调用
将验证码存入session
$validate_code=new ValidateCode([]); $validate_code->doimgAddWhiteBg(); \Yii::$app->session['validate_code']=$validate_code->getCode();
检验验证码是否正确,可以通过比对用户输入和session进行比对
public function actionCheck(){ $get=\Yii::$app->request->get(); if(isset($get['validate_code'])){ if(strtolower($get['validate_code'])== \Yii::$app->session['validate_code']){ echo "right"; }else{ echo "error"; } } }
注意:由于我们生成的验证码全都是强制存为小写,这里也要先把用户输入的验证码改为小写
前端验证码的调用
/verify/code是我们生成验证码图片的地址
点击图片的时候,更新图片地址,记得在图片地址后面加一个随机数,这样可以防止图片被缓存,而造成不更新的现象
<form method="get" action="/verify/check"> <input type="text" name="validate_code"><img src="/verify/code" onclick="this.src='/verify/code?'+Math.random()" alt="点击刷新" title="点击刷新"> <input type="submit"> </form>
最后看下我们的效果,还不错哈哈
数学运算验证码的原理和这个稍微有一些区别
简单起见,我只实现了个位数的加法,有兴趣的朋友可以做一些复杂的,
要实现数学运算,首先生成两个随即的个位数a和b,图片上面绘制的字符为a+b=
session中实际存储的为a+b的数值
生成一个数学运算的字符串addcode 和 结果code
private function createAddCode(){ $first=mt_rand(0,9); $second=mt_rand(0,9); $this->addcode=$first.'+'.$second.'='; $this->code=$first+$second; }
绘制addcode,前面我们绘制字符串的时候,让字符串随即倾斜了一个角度,而这里我就把倾斜角度设为0了,以免+号 被人看成了 乘法号,其他和图片验证码操作都一样
private function createAddFont(){ $_x=($this->width-10)/4; for($i=0;$i<4;$i++){ $this->fontcolor=imagecolorallocate($this->img,mt_rand(0,200),mt_rand(0,200),mt_rand(0,200)); imagettftext($this->img,$this->fontsize,0,$_x*$i+10+mt_rand(-5,15),$this->height / 1.4,$this->fontcolor,$this->font,$this->addcode[$i]); } }
来看看效果
图片验证码和数学运算验证码,相比之下,我更喜欢数学运算,因为用户输入很简单,只需输入1-2为数字即可,也不容易出错,而图片验证码一般要输入4位,特别是手机,感觉很麻烦
由于采用session存储验证码,会存在这样的情况,用户打开了两个注册页面,由于是同一个session,第一个页面的验证码显示的数值其实已经和当前的session不一致了,会造成用户输入验证错误,当然问题不算大, 只需点击图片更新即可,我也看了一下中国移动和中国联通的验证码,也都是存在这个情况。
有兴趣的朋友可以试着解决这个问题,比如请求验证码的时候带一个时间参数,存储session的时候每个验证码存的session key不同
点赞(0)