CAPTCHA pour codeigniter 4

Bonne après-midi! Malgré le titre de l'article, il présentera les méthodes et fonctions générales que j'ai utilisées pour créer mon captcha, qui peuvent être appliquées dans d'autres cadres avec des changements minimes. Certaines fonctions et approches sont basées sur les matériaux du poste de développement DIY CAPTCHA .

introduction


Pour travailler avec des images, vous devez vérifier la présence de la bibliothèque GD en PHP. Cela peut être fait en utilisant la fonction gd_info (). Dans les exemples présentés, j'utilise la version 2.1.0 et PHP 7.4.3, ce qui n'est pas nécessaire dans ce cas, car les fonctions PHP7 ne sont pas utilisées.

Logiques


Quel captcha dois-je voir? Celui qui m'aidera à réduire le nombre de demandes de serveur lors de l'autorisation dans mon site avec codeigniter 4.

Pour la mise en œuvre, l'image avec le code sera générée exclusivement côté serveur, enregistrée dans un dossier temporaire, encodée en base64 et retournée à l'utilisateur.

Développement


Avant de dessiner l'image, nous écrivons la méthode de génération de code.

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

Je note qu'à l'avenir, j'utiliserai différentes polices pour produire des caractères, vous devez donc faire attention à l'ensemble de caractères d'origine, ou aux polices elles-mêmes, afin d'éviter des problèmes avec des caractères tels que "Z" et "z", "X" et "x", «I» et «l», etc., car la déformation de l'image peut rendre l'entrée du captcha problématique.

Je déclare les champs nécessaires à l'avenir.

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

Je prépare des méthodes pour générer des arrière-plans et du bruit (liste complète à la fin).

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

Presque prêt. Nous écrivons le code secret dans l'image.

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

Nous combinons des méthodes toutes faites pour créer une image et vérifier le code.

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

En utilisant


Dans les contrôleurs nécessaires, nous préparons le captcha comme suit:

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

Dans le modèle, créez un jeton et un bouton pour régénérer l'image:

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

Nous ajoutons un itinéraire pour la demande ajax.

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

Et ajax lui-même.

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


Une palette de couleurs bruyante et un ensemble de distorsions avec différents paramètres compliquent grandement la solution du captcha. Après avoir joué avec les constantes, vous pouvez obtenir des résultats différents.

J'ai vérifié tous les résultats dans un éditeur graphique en lançant un seuil sur les images, mettant ainsi en évidence les caractères pouvant être utilisés pour la reconnaissance.



Le bruit et les lignes aléatoires augmentent bien entendu la complexité. Mais la conclusion que je tire est la suivante: si nécessaire et souhaitée, cette solution n'est pas complètement sûre, cependant, elle contribue à la protection contre les robots ordinaires.

Le code complet peut être consulté sur le github . Je serai heureux d'entendre des recommandations et des commentaires.

All Articles