不败君

前端萌新&初级后端攻城狮

PHP开发API接口的图形验证码

PHP开发API接口的图形验证码

2019-12-24 18:19:11

围观(8661)

在项目开发,可能会遇到这种情况:获取手机短信验证码前 需要先填写图形验证码 再发送短信到该用户的手机号。

1.png

这种情况的图形验证码是为了防刷短信,如果没有图形验证码 就可以很轻松的被利用接口疯狂发送短信,“短信轰炸机”就最喜欢这样的接口。


API 接口,生成验证码并验证的方法其实很多,比如有利用 Redis / 缓存 开发。


开始时,项目大佬说可以用 Session ,我表示是不信的,因为众所周知 Session 是依赖 cookie 的,Session 正常使用的前提是需要在 Cookie 存入一个 SessionId.所以在大佬坚信 Session 可实现该功能的情况下,博主开始了踩坑之路。


开始敲验证码功能的代码时,博主使用 Postman 测试是可以正常获取验证码 并 验证的。前端对接接口时,后端接口一直报错“验证码错误”,其实就是没有携带 Cookie 的原因,然后为了携带 Cookie 又导致了一连串的跨域问题。


故此,开发接口时,绝对不能依赖和使用 Session / Cookie 。


BTW,该项目开发的时候,用的是 TP5 框架 使用 Postman 可以正常使用 Session 因为携带了 Cookie. 而博主在质疑接口使用 Session 的时候有用 Laravel 框架进行测试。发现接口并不能设置、获取到 Session. 所以如果该项目用的是 Laravel 框架的话,在前期就会放弃用 Session 而不会继续踩坑。


回到正题。经过博主的踩坑后,给出的建议就是:在开发 API 接口的图形验证码时,可以使用 缓存 或者 Redis. 这里的缓存指的是:缓存系统

Laravel 的缓存系统还可以使用 Redis 作为驱动。如果不想要这样,可以直接读写 Redis 进行操作。


放上一个基于 TP 框架修改后的图形验证码生成方法(可用于任何地方 包括 Laravel 框架):

public function getCaptcha()
{
    $imageW = 0;

    $length = 5;

    $fontSize = 25;

    $imageH = 0;

    $image = null;

    $bg = [243, 251, 254];

    $color = null;

    $fontttf = '';

    $useImgBg = false;

    /********* 以上是配置项 *********/

    // 图片宽(px)
    $imageW || $imageW = $length * $fontSize * 1.5 + $length * $fontSize / 2;

    // 图片高(px)
    $imageH || $imageH = $fontSize * 2.5;

    // 建立一幅 $imageW x $imageH 的图像
    $image = imagecreate($imageW, $imageH);

    // 设置背景
    imagecolorallocate($image, $bg[0], $bg[1], $bg[2]);

    // 验证码字体随机颜色
    $color = imagecolorallocate($image, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150));

    // 验证码使用随机字体
    $ttfPath = public_path('ttfs') . '/';

    $dir  = dir($ttfPath);
    $ttfs = [];

    while (false !== ($file = $dir->read())) {
        if ('.' != $file[0] && substr($file, -4) == '.ttf') {
            $ttfs[] = $file;
        }
    }

    $dir->close();

    $fontttf = $ttfs[array_rand($ttfs)];

    $fontttf = $ttfPath . $fontttf;

    // 绘杂点
    $codeSet = '2345678abcdefhijkmnpqrstuvwxyz';
    for ($i = 0; $i < 10; $i++) {
        //杂点颜色
        $noiseColor = imagecolorallocate($image, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225));
        for ($j = 0; $j < 5; $j++) {
            // 绘杂点
            imagestring($image, 5, mt_rand(-10, $imageW), mt_rand(-10, $imageH), $codeSet[mt_rand(0, 29)], $noiseColor);
        }
    }

    // 绘干扰线
    $px = $py = 0;

    // 曲线前部分
    $A = mt_rand(1, $imageH / 2); // 振幅
    $b = mt_rand(-$imageH / 4, $imageH / 4); // Y轴方向偏移量
    $f = mt_rand(-$imageH / 4, $imageH / 4); // X轴方向偏移量
    $T = mt_rand($imageH, $imageW * 2); // 周期
    $w = (2 * M_PI) / $T;

    $px1 = 0; // 曲线横坐标起始位置
    $px2 = mt_rand($imageW / 2, $imageW * 0.8); // 曲线横坐标结束位置

    for ($px = $px1; $px <= $px2; $px = $px + 1) {
        if (0 != $w) {
            $py = $A * sin($w * $px + $f) + $b + $imageH / 2; // y = Asin(ωx+φ) + b
            $i  = (int)($fontSize / 5);
            while ($i > 0) {
                imagesetpixel($image, $px + $i, $py + $i, $color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多
                $i--;
            }
        }
    }

    // 曲线后部分
    $A   = mt_rand(1, $imageH / 2); // 振幅
    $f   = mt_rand(-$imageH / 4, $imageH / 4); // X轴方向偏移量
    $T   = mt_rand($imageH, $imageW * 2); // 周期
    $w   = (2 * M_PI) / $T;
    $b   = $py - $A * sin($w * $px + $f) - $imageH / 2;
    $px1 = $px2;
    $px2 = $imageW;

    for ($px = $px1; $px <= $px2; $px = $px + 1) {
        if (0 != $w) {
            $py = $A * sin($w * $px + $f) + $b + $imageH / 2; // y = Asin(ωx+φ) + b
            $i  = (int)($fontSize / 5);
            while ($i > 0) {
                imagesetpixel($image, $px + $i, $py + $i, $color);
                $i--;
            }
        }
    }

    // 绘验证码
    $code   = []; // 验证码
    $codeNX = 0; // 验证码第N个字符的左边距
    for ($i = 0; $i < $length; $i++) {
        $code[$i] = $codeSet[mt_rand(0, strlen($codeSet) - 1)];
        $codeNX += mt_rand($fontSize * 1.2, $fontSize * 1.6);
        imagettftext($image, $fontSize, mt_rand(-40, 40), $codeNX, $fontSize * 1.6, $color, $fontttf, $code[$i]);
    }

    // 验证码明文
    $plaintext_code = '';
    foreach ($code as $this_code) {
        $plaintext_code .= $this_code;
    }

    // 输出图像
    ob_start();
    imagepng($image);
    $content = ob_get_contents();
    ob_end_clean();
    imagedestroy($image);
    
    return [
        'content' => 'data:image/png;base64,' . base64_encode($content),
        'code' => $plaintext_code
    ];
}

调用上面的 getCaptcha 方法,可以获取到一个数组。

该数组的结构:

{
    "content": "data:image/png;base64,这里会是一串经过 Base64 编码的图形验证码",
    "code": "hckkn"
}

content 就是可以返回给前端的图形验证码编码。

2.png

code 就是验证码的明文,可以将验证码明文存入 Redis.

存入 Redis 的时候,可以将客户端的 IP 地址作为 Key 验证码明文的 Code 作为 Value.

验证时,就取该 IP 地址对应的 Key,如果不存在该 Key 或者 Value 不对,就是验证码不正确。

另外还需要注意,使用 Redis 设置一个键值对的验证码时,需要给过期时间。

还有要注意的就是,判断验证码正确时,应当销毁 Redis 已经通过验证的记录。


使用 getCaptcha 方法还需要几个字体文件,字体文件下载地址: 百度云

如果要改字体存放目录,可以修改上方代码中的这句

$ttfPath = public_path('ttfs') . '/';


本文地址 : bubaijun.com/page.php?id=156

版权声明 : 未经允许禁止转载!

评论:我要评论
发布评论:
Copyright © 不败君 粤ICP备18102917号-1

不败君

首 页 作 品 微 语