验证码4的验证码

下午好!尽管有本文的标题,它仍将介绍我用于创建验证码的常规方法和功能,这些方法和功能可以用最少的更改将其应用于其他框架。一些功能和方法基于DIY CAPTCHA开发岗位的材料

介绍


要使用图像,您需要检查PHP中GD库的存在。可以使用gd_info()函数来完成。在给出的示例中,我使用版本2.1.0和PHP 7.4.3,在这种情况下这不是必需的,因为未使用PHP7函数。

逻辑学


我想看什么验证码?一个可以帮助我减少使用codeigniter 4在我的站点中进行授权期间服务器请求的数量。

对于实现,带有代码的图片将在服务器端专门生成,保存到一个临时文件夹中,以base64编码并返回给用户。

发展历程


在绘制图片之前,我们编写代码生成方法。

public function generate_code() {
        srand((float) microtime() * 1000000);
        $chars = 'ABDEFHKNRSTYZabdefhknrstyz23456789'; //   
        $length = rand(5, 7); //  
        $numChars = strlen($chars); 
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= $chars[rand(0, $numChars - 1)];
        }
        $array_mix = preg_split('//', $str, -1, PREG_SPLIT_NO_EMPTY);
        shuffle($array_mix);
        delete_cookie('cap'); //  ,   ,      
        set_cookie('cap', md5(implode("", $array_mix)), self::$_code_time); //      md5   _code_time
        return implode("", $array_mix);
    }

我注意到,将来我会使用各种字体来输出字符,因此您需要注意原始字符集或字体本身,以避免出现诸如“ Z”和“ z”,“ X”和“ x”之类的字符的问题, “ I”和“ l”等,因为图像失真会使验证码输入成为问题。

我声明将来必填字段。

public static $width = 220; //    
public static $height = 120; //  
public static $fonts_num = 4; //     /public/fonts/
private static $_code_time = 180; //     .

我正在准备一些产生背景和噪音的方法(最后有完整的清单)。

/**
     *    .
     *
     * $mode == "parallel",      
     * $max —   
     */
private function _add_line($img, $mode = '', $max = 100) {
    for ($i = 0; $i < rand(0, $max); $i++) {
        $color = imagecolorallocate($img, rand(80, 150), rand(80, 150), rand(80, 150));
        if ($mode === 'parallel') {
            $r1 = rand(0, self::$width);
            $r2 = rand(0, self::$width);
            imageline($img, $r1, $r1, $r2, $r1, $color);
            imageline($img, $r1, $r2, $r1, rand(0, 220), $color);
        } else {
            imageline($img, rand(0, self::$width), rand(0, self::$width), rand(0, self::$width), rand(0, self::$width), $color);
        }
    }
}

private function _add_poly($img) { //   
    $points = [];
    for ($i = 0; $i < 10; $i++) {
        array_push($points, rand(0, self::$width * 2));
    }
    $color = imagecolorallocate($img, rand(80, 190), rand(80, 190), rand(80, 190));
    imageFilledPolygon($img, $points, 5, $color);
}

/**
     *    .
     *
     * $xn  $yn     .   ,   ,   .  ,     "".
     * $mode          ('normal').
     */
private function _set_glitch_color($image, $xn = 0, $yn = 0, $mode = 'normal') {
    $start = rand(self::$height / 2, self::$height / 2 - self::$height / 4);
    $finish = $start + rand(5, 15);
    for ($x = 0; $x < self::$width - 1; $x++) {
        for ($y = 0; $y < self::$height - 1; $y++) {
            if ($mode != 'normal') {
                $xn = rand(0, 1);
                $yn = rand(0, 1);
            } else {
                $finish = $start + 3;
            }
            if ($y > $start && $y < $finish) {
                imagesetpixel($image, $x + $xn, $y + $yn, imagecolorat($image, $x, $y));
            }
        }
    }
}

差不多好了。我们在图片中写上密码。

private function _add_text($img, $text) {
    $x = rand(10,20); //    X   .
    for ($i = 0; $i < strlen($text); $i++) {
        $text_color = imagecolorallocate($img, rand(150, 250), rand(150, 250), rand(150, 250));
        imagettftext($img, rand(35, 40), rand(0, 10) - rand(0, 10), $x, rand(55, 95), $text_color, 'fonts/' . rand(1, self::$fonts_num) . ".ttf", $text[$i]); //       1.ttf  *self::$fonts_num*.ttf
        $x += rand(25, 35);
    }
}

我们结合了现成的方法来创建图片和检查代码。

public function img_code($code) {
    $image = imagecreatetruecolor(self::$width, self::$height);
    imageantialias($image, true);
    $rand_color = imagecolorallocate($image, rand(50, 120), rand(50, 120), rand(50, 120));
    imagefilledrectangle($image, 0, 0, self::$width, self::$height, $rand_color);
    $this->_add_rand_bg($image); //   
    $this->_add_text($image, $code); 
    $this->_add_glitch($image, 'normal');
    $this->_add_glitch($image, 'boom');
    $this->_add_line($image, 'rand', 200); //    
    $file = 'temp/' . md5($code) . ".png"; 
    imagepng($image, $file); //  
    imagedestroy($image);
    $res = base64_encode(file_get_contents($file)); //    base64()
    unlink($file); //  
    return $res;
}

public function check($tested) {
    $cap = get_cookie('cap');
    $r['error'] = '';
    if (!$cap) { //   
        $r['error'] = '    .';
    } elseif (strcmp($tested, $cap)) {
        $r['error'] = '   .';
    }
    delete_cookie('cap'); //     
    return $r;
}

使用


在必要的控制器中,我们按如下方式准备验证码:

public function __construct() {
    ...
    $this->captcha = new \App\Libraries\Captcha();
    ...
}

public function some_function() {
    ...
    $data['captcha'] = $this->captcha->img_code( $this->captcha->generate_code() );
    return view('page/template', $data);
}

public function recaptcha(){ //  ajax-
    return $this->captcha->img_code( $this->captcha->generate_code() );
}

在模板中,创建一个令牌和一个按钮以重新生成图片:

...
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" id="csrf"/>
...
<img src="data:image/png;base64,<?= $captcha ?>" id="cap" width="220" height="120"/>
<div id="ref" onclick="recaptcha()">⥁</div>
...

我们为ajax请求添加一条路由。

$routes->post('/recap', 'AuthController::recaptcha');

还有ajax本身。

var numlog = 0;
function recaptcha() {
    if(numlog <= 5){
        $.ajax({
            type: 'post',
            url: '/recap',
            data: {csrf_token: $('#csrf').val()},
            success: function (result) {
                numlog++;
                $('#cap').attr('src', "data:image/png;base64," + result + '');
            }
        });
    }else{
        $('#ref').css('display', 'none');
    }
}


嘈杂的调色板和一组具有不同参数的失真会使验证码的解决方案大大复杂化。使用常量后,您可以获得不同的结果。

我在图形编辑器中检查了所有结果,方法是在图片上设置阈值,从而突出显示可用于识别的字符。



当然,噪声和随机线会增加复杂性。但是我得出以下结论:如果有必要和期望,此解决方案并不完全安全,但是,它有助于防止普通的僵尸程序。

完整的代码可以在github上查看我将很高兴听到建议和评论。

All Articles