美文网首页PHP的学习
【PHP】一步一步实现验证码

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

作者: dy2903 | 来源:发表于2017-04-30 17:15 被阅读155次

    背景

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

    很多地方需要用到验证码,因为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手册中的详细用法摘录如下:

    • imagecreatetruecolor — 新建一个真彩色图像
      resource imagecreatetruecolor ( int $width , int $height )
    • imagecolorallocate — 为一幅图像分配颜色
      int imagecolorallocate ( resource $image , int $red , int $green , int $blue )
      第一次对 imagecolorallocate() 的调用会给基于调色板的图像填充背景色,即用 imagecreate() 建立的图像。
      其他red,green,blue对应RGB值
    • imagefill — 区域填充
      bool imagefill ( resource $image , int $x , int $y , int $color )
      imagefill() 在 image 图像的坐标 x,y(图像左上角为 0, 0)处用 color 颜色执行区域填充(即与 x, y 点颜色相同且相邻的点都会被填充)。
    • header — 发送原生 HTTP 头
      void header ( string $string [, bool $replace = true [, int $http_response_code ]] )
      请注意 header() 必须在任何实际输出之前调用
    • imagepng — 以 PNG 格式将图像输出到浏览器或文件
      bool imagepng ( resource $image [, string $filename ] )
    • imagedestroy — 销毁图片资源
      bool imagedestroy ( resource $image )

    注意事项:

    • 依赖于GD扩展,输出图片前必须提前输出图片header信息
    • 默认输出为黑色背景。

    实现数字验证码

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

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

    • font:表示字体,
    • x,y:表示位置

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

    因为要生成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>

    动态验证

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

    • 增加可点击的“换一个”文案
    • 用JS选择器选取图片
    • 用js修改验证码图片地址
      首先增加文案
      <pre>
      <a href="javascript:void(0)">换一个?</a>
      </pre>
      为了方便选取图片,给img加上id属性,使用document.getElementById('captch_img')获得图片,然后用新的图片对他进行赋值,注意可以加上一段随机数字。
      <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();
      }
      ?>
      <!doctype html>
      <html lang="en">
      <head>
      <meta charset="UTF-8">
      <title>确认验证码</title>
      </head>
      <body>
      <form action="./form.php" method="post">
      <p>验证图片:<img id="captcha_img" border="1" src="./captcha.php?r=<?php echo rand();?>" width="100" heigh="30">
      <a href="javascript:void(0)" onclick="document.getElementById('captcha_img').src='./captcha.php?r='+Math.random()">换一个?</a>
      </p>
      <p>请输入图片中的内容:<input type="text" name="authcode" value=""/></p>
      <p><input type="submit" value="提交" style="padding:6px 20px;"/></p>
      </form>
      </body>
      </html>
      </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>
      /* 内部使用的私有方法,用来创建图像资源,并初使化背影 */
      private function getCreateImage(){
      $this->image = imagecreatetruecolor($this->width,$this->height);

            $backColor = imagecolorallocate($this->image, rand(225,255),rand(225,255),rand(225,255)); 
            
            @imagefill($this->image, 0, 0, $backColor); 
            
        }
      

    </pre>

    • 设置干扰元素
      <pre>
      /* 内部使用的私有方法,设置干扰像素,向图像中输出不同颜色的点 */
      private function setDisturbColor() {
      // 画随机点
      for($i=0; $i <= $this->disturbColorNum; $i++) {
      $color = imagecolorallocate($this->image, rand(50,200), rand(50,200), rand(50,200));
      imagesetpixel($this->image,rand(1,$this->width-2),rand(1,$this->height-2),$color);
      }
      // 画随机线
      for($i=0; $i<10; $i++){
      $color=imagecolorallocate($this->image,rand(80,220),rand(80,220),rand(80,220));
      imageline($this->image,rand(-10,$this->width),rand(-10,$this->height),rand(30,300),rand(20,200),55,44,$color);
      }
      }
      </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>

    • 使用outImg()函数统一起来

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

    • 向服务器SESSION中保存验证码
      <pre>
      /**
      * 用于输出验证码图片,也向服务器的SESSION中保存了验证码,使用echo 输出对象即可
      /
      function __toString(){
      /
      加到session中, 存储下标为code */
      $_SESSION["code"] = strtoupper($this->checkCode);
      $this->outImg();
      return '';
      }
      </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>

    相关文章

      网友评论

        本文标题:【PHP】一步一步实现验证码

        本文链接:https://www.haomeiwen.com/subject/oitxtxtx.html