PHP的学习

【PHP】一步一步实现验证码

2017-04-30  本文已影响155人  dy2903

背景

验证码就是把一串随机产品的数字动态生成一幅图片,再加上干扰元素。此时用户可以通过肉眼能识别里面的数字或者字符,但是可以屏蔽机器的自动识别。

很多地方需要用到验证码,因为Web网站经常会遇到身份欺骗的攻击,攻击者通过在客户端脚本写入代码,写请求消耗远远大于读请求。大量写请求的情况,会严重耗费系统的资源。所以说验证码主要是防止有人利用机器人进行自动批量注册等行为。

笔者看过了http://www.imooc.com/learn/115
的视频教程以后非常有感触,想把实现验证码的步骤一步一步的记录下来。同时最后的时候他封装成类,可以在之后的工作中进行反复的利用。

其实验证码的代码已经研究了很多次了,但是大部分网上找到的教程都是一次性给出所有的代码,这样阅读的时候很难把握住笔者的开发思路,本教程希望一步一步的截图上代码,帮助新人理清思路。

验证码的核心技术

首先需要对实现验证码的步骤进行分解。主要可以分为如下几步:

Paste_Image.png

接下来我们分不同的章节介绍如何实现每一个步骤。

环境

项目 内容
操作系统 windows
环境 xamp

需要启动Apache,验证方法是在浏览器框里输入127.0.0.1
如果出现如下的图片,则启动成功。


Paste_Image.png

实现验证码底图

首先是通过PHP实现100×30px大小的图片。
主要使用的函数方法:resource imagecreatetruecolor(int $width, int $height)
在D:\xampp\htdocs下建立project文件夹。新建captcha.php
输入:

<?php $image = imagecreatetruecolor(100,30);//生成资源 $bgcolor = imagecolorallocate($image , 255,255,255);//#ffffff,为一幅图像分配颜色 imagefill($image , 0 , 0 , $bgcolor);//左上角,区域填充 header("content-type:image/png"); imagepng($image); imagedestroy($image); ?>
在浏览器输入框输入http://127.0.0.1/project/captcha.php

Paste_Image.png

结果是一片大白。

下面解释一下里面涉及到的函数:

项目 内容
imagecreatetruecolor 生成空白的画布
imagecolorallocate 分配颜色
imagefill 填充颜色
header 发送HTTP头
imagepng 以png格式进行导出
imagedestory 输出结束的时候记得回收资源

为了表明各函数用法,特将PHP手册中的详细用法摘录如下:

注意事项:

实现数字验证码

在上一章里面我们已经新建了一张画布,相当于把整个框架搭起来了,本章的主要目的是在上面加上随机的数字。
给图片加上数字,主要使用imagestring()方法。

imagestring — Draw a string horizontally
bool imagestring ( resource $image , int $font , int $x , int $y , string $string , int $color )

从上面这个函数的参数可以看出,我们需要给出生成随机验证码数字的字体,位置,内容和颜色。
字体是死的,可以直接规定大小。

因为要生成4个随机数字,可以使用一个for循环for ($i = 0 ; $i < 4 ; $i++)

下面讨论怎么获得位置信息:
可以把长度100分成4份,所以每个数字的位置是($i * 100 / 4 ),然后加上随机的offset,可以得到
$x = ($i * 100 / 4 ) + rand (5,10);

内容通过:$fontContent = rand (0,9);获得。

最后是怎么获得随机的颜色:
对于imagecolorallocate()这个函数而言,只需要获得随机的RGB值,就可以实现随机的颜色了。为了更加醒目,我们限定随机RGB值的范围为0~120,因为是深色区间。

$fontColor = imagecolorallocate($image , rand(0,120) , rand(0,120), rand(0,120));//0~120是深色区间。

下面加上生成随机数字的代码段,在imagefill()那行下面加入:
<?php $image = imagecreatetruecolor(100,30);//生成资源 $bgcolor = imagecolorallocate($image , 255,255,255);//#ffffff,为一幅图像分配颜色 imagefill($image , 0 , 0 , $bgcolor);//左上角,区域填充 // 生成随机数字 for ($i = 0 ; $i < 4 ; $i++){ $fontSize = 6; $fontColor = imagecolorallocate($image , rand(0,120) , rand(0,120), rand(0,120));//0~120是深色区间。 $fontContent = rand (0,9); // 坐标 $x = ($i * 100 / 4 ) + rand (5,10);//总宽度100,放4个数字。 $y = rand (5 , 10); imagestring ($image,$fontSize,$x,$y,$fontContent,$fontColor); } header("content-type:image/png"); imagepng($image); imagedestroy($image); ?>

在地址栏里面输入127.0.0.1/project/captcha.php

Paste_Image.png

可以看出生成了4个随机的数字。

增加干扰元素

为了防止被机器轻易的读出来,可以增加干扰的元素。
先增加干扰的小点,用到函数
bool imagesetpixel(resource $image , int $x , int $y , int $color)

首先看点的位置怎么得到。
既然是随机生成用来干扰的点,那么可以在整个画布随机的跑,所以x = rand (1 , 99); y = rand (1 , 29)
而点的颜色注意要比字的浅,所以在50~200中选。

上代码,增加200个点。

<?php $image = imagecreatetruecolor(100,30);//生成资源 $bgcolor = imagecolorallocate($image , 255,255,255);//#ffffff,为一幅图像分配颜色 imagefill($image , 0 , 0 , $bgcolor);//左上角,区域填充 for ($i = 0 ; $i < 4 ; $i++){ $fontSize = 6; $fontColor = imagecolorallocate($image , rand(0,120) , rand(0,120), rand(0,120));//0~120是深色区间。 $fontContent = rand (0,9); // 坐标 $x = ($i * 100 / 4 ) + rand (5,10);//总宽度100,放4个数字。 $y = rand (5 , 10); imagestring ($image,$fontSize,$x,$y,$fontContent,$fontColor); } for ($i = 0 ; $i < 200 ; $i++){ $pointColor = imagecolorallocate ($image , rand (50,200),rand(50,200),rand(50,200));//颜色要比数字的浅。 imagesetpixel ($image , rand (1,99),rand(1,29),$pointColor);//生成的点不要超过画布。 } header("content-type:image/png"); imagepng($image); imagedestroy($image); ?>
依旧看看效果:

Paste_Image.png

再加点随机的线:
使用方法:bool imageline (resource $image , int $x1 , int $y1 , int $x2 , int $y2 , int $color)
手册上的解释是:
Draws a line between the two given points.
也就是说在给出的两点间画线。

注意随机线的颜色比随机点的颜色要更浅,所以在80~220里面选。
下面给出加随机线的代码:
// 生成干扰线 for ( $i = 0 ; $i < 3 ; $i++){ $lineColor = imagecolorallocate($image , rand (80,220),rand(80,220),rand(80,220)); imageline($image , rand (1,99),rand(1,29),rand(1,99),rand(1,29),$lineColor); }
结果如图:

Paste_Image.png

生成随机的字母和数字

之前生成的内容只有随机的数字,还是容易被机器识别出来,所谓道高一尺魔高一丈,对手在加码,我们也得加码撒。下面我们在随机的内容里面加上字母,这样组合更多,可以更好的屏蔽那些机器人。

首先我们把可能用到的字母和数字放到变量$data里面去,注意可以把容易混淆的字母和数字去掉,比如l和数字1容易混,所以直接干掉。

然后每次随机的从字符串里面抽一个出来,使用substr函数。
string substr ( string $string , int $start [, int $length ] )
从$string里$start位置抽出$length长的字符。

这样对原有的代码进行简单的修改就可以了。

<?php $image = imagecreatetruecolor(100,30);//生成资源 $bgcolor = imagecolorallocate($image , 255,255,255);//#ffffff,为一幅图像分配颜色 imagefill($image , 0 , 0 , $bgcolor);//左上角,区域填充 // 生成随机数字 for ($i = 0 ; $i < 4 ; $i++){ $fontSize = 6; $fontColor = imagecolorallocate($image , rand(0,120) , rand(0,120), rand(0,120));//0~120是深色区间。 // $fontContent = rand (0,9); $data = "abcdefghijkmnopqrstuvwxyz23456789"; // 生成随机的字母和数字,从$data字符串里面,任意一个位置,取一个字母 $fontContent = substr($data , rand(0,strlen($data)), 1); // 坐标 $x = ($i * 100 / 4 ) + rand (5,10);//总宽度100,放4个数字。 $y = rand (5 , 10); imagestring ($image,$fontSize,$x,$y,$fontContent,$fontColor); } for ($i = 0 ; $i < 200 ; $i++){ $pointColor = imagecolorallocate ($image , rand (50,200),rand(50,200),rand(50,200));//颜色要比数字的浅。 imagesetpixel ($image , rand (1,99),rand(1,29),$pointColor);//生成的点不要超过画布。 } // 生成干扰线 for ( $i = 0 ; $i < 3 ; $i++){ $lineColor = imagecolorallocate($image , rand (80,220),rand(80,220),rand(80,220)); imageline($image , rand (1,99),rand(1,29),rand(1,99),rand(1,29),$lineColor); } header("content-type:image/png"); imagepng($image); imagedestroy($image); ?>
效果如图

Paste_Image.png

通过session存储验证信息

session是存储的服务器端的信息,可以把生成的内容保存在服务器端的$_SESSION["authcode"]字段里面。当前端的用户输入相应的验证字符后,和$_SESSION["authcode"]进行对比,如果相同,自然证明输入正确、

需要在代码的最顶部加上session_start()

现在要考虑如何保存保存验证码的信息,先定义一个$captch_code 的空字符串,每生成一的随机的数字或者字符,都拼接到上面去。

其实在真实的开发环境下,我们都是用服务器的集群来保存session的,也就是说有可能第一次请求的时候建立的session保存在A服务器上,但是下次需要来拿数据的时候会被分配到B服务器上,所以校验会失败。在多服务器的情况下,我们需要考虑集中管理session,比如使用memcache来保存session信息。

<?php session_start(); $image = imagecreatetruecolor(100,30);//生成资源 $bgcolor = imagecolorallocate($image , 255,255,255);//#ffffff,为一幅图像分配颜色 imagefill($image , 0 , 0 , $bgcolor);//左上角,区域填充 // 生成随机数字 $captch_code = ""; for ($i = 0 ; $i < 4 ; $i++){ $fontSize = 6; $fontColor = imagecolorallocate($image , rand(0,120) , rand(0,120), rand(0,120));//0~120是深色区间。 // $fontContent = rand (0,9); $data = "abcdefghijkmnopqrstuvwxyz23456789"; // 生成随机的字母和数字,从$data字符串里面,任意一个位置,取一个字母 $fontContent = substr($data , rand(0,strlen($data)), 1); $captch_code .= $fontContent; // 坐标 $x = ($i * 100 / 4 ) + rand (5,10);//总宽度100,放4个数字。 $y = rand (5 , 10); imagestring ($image,$fontSize,$x,$y,$fontContent,$fontColor); } // 将循环生成的随机数字保存在$_SESSION里面 $_SESSION['authcode'] = $captch_code; for ($i = 0 ; $i < 200 ; $i++){ $pointColor = imagecolorallocate ($image , rand (50,200),rand(50,200),rand(50,200));//颜色要比数字的浅。 imagesetpixel ($image , rand (1,99),rand(1,29),$pointColor);//生成的点不要超过画布。 } // 生成干扰线 for ( $i = 0 ; $i < 3 ; $i++){ $lineColor = imagecolorallocate($image , rand (80,220),rand(80,220),rand(80,220)); imageline($image , rand (1,99),rand(1,29),rand(1,99),rand(1,29),$lineColor); } header("content-type:image/png"); imagepng($image); imagedestroy($image); ?>

验证码通过表单进行提交

在project文件夹里面新建form.php
输入
<pre>
<?php?>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>确认验证码</title>
</head>
</pre>
<body> <form action="./form.php" method="post"> <p>验证图片:![](./captcha.php)</p> <p>请输入图片中的内容:<input type="text" name="authcode" value=""/></p> <p><input type="submit" value="提交" style="padding:6px 20px;"/></p> </form> </body> </html>

表单提交的内容都会存到$_REQUEST['authcode']里面,所以需要加上验证表单提交和$_SESSION[‘authcode’]是否相同的代码。
<pre>
<?php
if (isset($_REQUEST['authcode'])){
session_start();
if($_REQUEST['authcode'] == $_SESSION['authcode']){
echo '<font color="#0000CC">输入正确</font>';
}else{
echo '<font color = "#CC0000"><b>输入错误</b></font>';
}
exit();
}
?>
</pre>

此时在地址栏输入127.0.0.1/project/form.php,然后填入验证码字符

Paste_Image.png

点击提交按钮。

Paste_Image.png

但是如果是输入全大写的字母,会输出“输入错误”的标志。

因为我们在判断的时候是把原始输入和服务器保存的进行比较,所以自然需要完全一致才行。
可以把用户的输入全部转换为小写
<pre>
<?php
if (isset($_REQUEST['authcode'])){
session_start();
if(strtolower(trim($_REQUEST['authcode'])) == $_SESSION['authcode']){
echo '<font color="#0000CC">输入正确</font>';
}else{
echo '<font color = "#CC0000"><b>输入错误</b></font>';
}
exit();
}
?>
</pre>

动态验证

之前的代码存在一个问题,如果出来的验证码图片用户不能完全分辨得出,就没有办法了。在生活中,一般的验证码图片旁边都会有“看不清?”这样的提示,点击的话,会刷新验证码。
可以通过三个步骤进行:

至此验证码的功能基本都实现了,下面主要进行类的封装,以后可以多次的复用。

验证码类的封装

首先考虑通用的验证码类里面有那些元素:

<pre>
class Vcode {
private $width; //验证码图片的宽度
private $height; //验证码图片的高度
private $codeNum; //验证码字符的个数
private $disturbColorNum; //干扰元素数量
private $checkCode; //验证码字符
private $image; //验证码资源
}
</pre>

然后写构造方法,主要用来实例化验证码对象,并为一些成员属性初使化 。
在写构造方法之前,我们新定义一个内部私有方法createCheckCode(),随机生成用户指定个数的字符串,去掉了容易混淆的字符oOLlz和数字012
<pre>
private function createCheckCode(){
$data="3456789abcdefghijkmnpqrstuvwxy";
for($i=0; $i<$this->codeNum; $i++) {
// $char = $code{rand(0,strlen($code)-1)};
$fontContent = substr($data , rand(0,strlen($data)), 1);
$captch_code .= $fontContent;
}
return $captch_code;
}
</pre>

然后开始写构造函数:
<pre>
/**
* 构造方法用来实例化验证码对象,并为一些成员属性初使化
* @param int $width 设置验证码图片的宽度,默认宽度值为80像素
* @param int $height 设置验证码图片的高度,默认高度值为20像素
* @param int $codeNum 设置验证码中字母和数字的个数,默认个数为4个
/
function __construct($width=80, $height=20, $codeNum=4) {
$this->width = $width;
$this->height = $height;
$this->codeNum = $codeNum;
$number = floor($height
$width/15);
//如果验证码字符过多,则需要减少干扰点的数量
if($number > 240-$codeNum)
$this->disturbColorNum = 240-$codeNum;
else
$this->disturbColorNum = $number;
$this->checkCode = $this->createCheckCode();
}
</pre>

我们之前讲过输出图像有几个步骤,首先新建画布,然后生成干扰点,再加上随机的字符,最后输出。可以把每个步骤包装成一个方法,然后在outImg()里面统一调用。

</pre>

<pre>
/* 内部使用的私有方法,随机颜色、随机摆放、随机字符串向图像中输出 /
private function outputText() {
for ($i=0; $i<=$this->codeNum; $i++) {
$fontcolor = imagecolorallocate($this->image, rand(0,128), rand(0,128), rand(0,128));
$fontSize = rand(3,5);
$x = floor($this->width/$this->codeNum)
$i+3;
$y = rand(0,$this->height-imagefontheight($fontSize));
imagechar($this->image, $fontSize, $x, $y, $this->checkCode{$i}, $fontcolor);
}
}
</pre>

<pre>
/* 内部使用的私有方法,自动检测GD支持的图像类型,并输出图像 */
private function outputImage(){
if(imagetypes() & IMG_GIF){
header("Content-type: image/gif");
imagegif($this->image);
}elseif(imagetypes() & IMG_JPG){
header("Content-type: image/jpeg");
imagejpeg($this->image, "", 0.5);
}elseif(imagetypes() & IMG_PNG){
header("Content-type: image/png");
imagepng($this->image);
}elseif(imagetypes() & IMG_WBMP){
header("Content-type: image/vnd.wap.wbmp");
imagewbmp($this->image);
}else{
die("PHP不支持图像创建!");
}
}
</pre>

<pre>
/* 内部使用的私有方法,用于输出图像 */
private function outImg(){
$this->getCreateImage();
$this->setDisturbColor();
$this->outputText();
$this->outputImage();
}
</pre>

<pre>
/* 析构方法,在对象结束之前自动销毁图像资源释放内存 */
function __destruct(){
imagedestroy($this->image);
}
</pre>

至此验证码类已经完成,只要直接输出对象,就可以向浏览器中输出图片,可以在表单中使用。

在提交验证码到服务器的时候,已经转为了大写,需要在验证的时候把浏览器提交的也转换为大写。

应用验证码类

新建imgcode.php文件,使用session_start()开启回话控制,同时创建类的对象。
此时如果使用echo输出,同时会自动将验证码字符串保存在服务器中。

<pre>
<?php
/**
file:imgcode.php
用于请求时,通过验证码类的对象向客户端输出图片
*/
session_start(); //开启SESSION,会使用$_SESSION["code"]在服务器中保存验证码

require_once('captch.class.php');    //包含验证码所在的类文件
echo new Vcode();                   //创建验证码对象,并直接被输出自动调用魔术__toString()方法

</pre>

构造表单,应用验证码

在form.php中,包含输入表单和匹配验证码两部分。

<pre>
<?php
/** form.php 用于输出用户操作表单和验证用户的输入 /
session_start(); //开启SESSION
if(isset($_POST['submit'])){ //判断用户提交后执行
/
判断用户在表单中输入的字符串和验证码图片中的字符串是否相同 /
if(strtoupper(trim($_POST["code"])) == $_SESSION['code']){ //如果验证码输出成功
echo '验证码输入成功
'; //输出成功的提示信息
}else{ //如果验证码输入失败
echo '<font color="red">验证码输入错误!!</font>
'; //输出失败的输入信息
}
}
?>
<html>
<head>
<title>Image</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<script>
/
定义一个JavaScript函数,当单击验证码时被调用,将重新请求并获取一个新的图片 /
function newgdcode(obj,url) {
/
后面传递一个随机参数,否则在IE7和火狐下,不刷新图片 */
obj.src = url+ '?nowtime=' + new Date().getTime();
}
</script>
</head>
<body> ![](imgcode.php) <form method="POST" action="form.php"> <input type="text" size="4" name="code" /> <input type="submit" name="submit" value="提交"> </form> </body> </html>

上一篇下一篇

猜你喜欢

热点阅读