CAPTCHA untuk pemberi kode 4

Selamat sore! Terlepas dari judul artikel, artikel ini akan menyajikan metode dan fungsi umum yang saya gunakan untuk membuat captcha saya, yang dapat digunakan dalam kerangka kerja lain dengan perubahan minimal. Beberapa fungsi dan pendekatan didasarkan pada materi pos pengembangan CAPTCHA DIY .

pengantar


Untuk bekerja dengan gambar, Anda perlu memeriksa keberadaan perpustakaan GD di PHP. Ini dapat dilakukan dengan menggunakan fungsi gd_info (). Dalam contoh yang disajikan, saya menggunakan versi 2.1.0 dan PHP 7.4.3, yang tidak perlu dalam kasus ini, karena fungsi PHP7 tidak digunakan.

Logika


Apa captcha yang ingin saya lihat? Salah satu yang akan membantu saya mengurangi jumlah permintaan server selama otorisasi di situs saya dengan codeigniter 4.

Untuk implementasi, gambar dengan kode akan dihasilkan secara eksklusif di sisi server, disimpan ke folder sementara, disandikan dalam base64 dan dikembalikan ke pengguna.

Pengembangan


Sebelum menggambar, kita menulis metode pembuatan kode.

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);
    }

Saya perhatikan bahwa di masa depan saya akan menggunakan berbagai font untuk menampilkan karakter, jadi Anda perlu memperhatikan set karakter asli, atau font sendiri, untuk menghindari masalah dengan karakter seperti "Z" dan "z", "X" dan "x", "I" dan "l", dll., Karena mendistorsi gambar dapat membuat input captcha bermasalah.

Saya menyatakan bidang yang diperlukan di masa depan.

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

Saya sedang mempersiapkan beberapa metode untuk menghasilkan latar belakang dan kebisingan (daftar lengkap di bagian akhir).

/**
     *    .
     *
     * $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));
            }
        }
    }
}

Hampir siap. Kami menulis kode rahasia di gambar.

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);
    }
}

Kami menggabungkan metode yang sudah jadi untuk membuat gambar dan memeriksa kode.

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;
}

Menggunakan


Pada pengontrol yang diperlukan, kami menyiapkan captcha sebagai berikut:

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() );
}

Di templat, buat token dan tombol untuk menghasilkan kembali gambar:

...
<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>
...

Kami menambahkan rute untuk permintaan ajax.

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

Dan ajax sendiri.

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');
    }
}

Total


Palet warna yang berisik dan serangkaian distorsi dengan parameter berbeda sangat menyulitkan solusi captcha. Setelah bermain dengan konstanta, Anda bisa mendapatkan hasil yang berbeda.

Saya memeriksa semua hasil dalam editor grafis dengan melemparkan ambang pada gambar, dengan demikian menyoroti karakter yang dapat digunakan untuk pengakuan.



Garis kebisingan dan acak, tentu saja, menambah kerumitan. Tetapi kesimpulan yang saya buat adalah sebagai berikut: jika perlu dan diinginkan, solusi ini tidak sepenuhnya aman, namun membantu melindungi dari bot biasa.

Kode lengkap dapat dilihat di github . Saya akan senang mendengar rekomendasi dan komentar.

All Articles