CAPTCHA para codeigniter 4

Boa tarde! Apesar do título do artigo, ele apresentará os métodos e funções gerais que usei para criar meu captcha, que podem ser aplicados em outras estruturas com alterações mínimas. Algumas funções e abordagens são baseadas nos materiais do posto de desenvolvimento DIY CAPTCHA .

Introdução


Para trabalhar com imagens, você precisa verificar a presença da biblioteca GD no PHP. Isso pode ser feito usando a função gd_info (). Nos exemplos apresentados, eu uso a versão 2.1.0 e o PHP 7.4.3, o que não é necessário neste caso, pois as funções do PHP7 não são utilizadas.

Logics


Que captcha eu quero ver? Um que me ajudará a reduzir o número de solicitações de servidor durante a autorização no meu site com o codeigniter 4.

Para implementação, a imagem com o código será gerada exclusivamente no lado do servidor, salva em uma pasta temporária, codificada em base64 e retornada ao usuário.

Desenvolvimento


Antes de desenhar a imagem, escrevemos o método de geração de código.

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

Observo que, no futuro, usarei várias fontes para gerar caracteres, portanto, você deve prestar atenção ao conjunto original de caracteres ou às próprias fontes, a fim de evitar problemas com caracteres como "Z" e "z", "X" e "x", "I" e "l" etc., pois distorcer a imagem pode tornar a entrada do captcha problemática.

Declaro os campos necessários no futuro.

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

Estou preparando alguns métodos para gerar fundos e ruído (lista completa no final).

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

Quase pronto. Escrevemos o código secreto na imagem.

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

Combinamos métodos prontos para criar uma imagem e verificar o código.

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

Usando


Nos controladores necessários, preparamos o captcha da seguinte maneira:

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

No modelo, crie um token e um botão para gerar novamente a imagem:

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

Nós adicionamos uma rota para solicitação de ajax.

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

E ajax em si.

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


Uma paleta de cores barulhenta e um conjunto de distorções com parâmetros diferentes complica bastante a solução do captcha. Depois de brincar com as constantes, você pode obter resultados diferentes.

Eu verifiquei todos os resultados em um editor gráfico, jogando um limite nas figuras, destacando os caracteres que podem ser usados ​​para reconhecimento.



Ruído e linhas aleatórias, é claro, aumentam a complexidade. Mas a conclusão que faço é a seguinte: se necessário e desejado, essa solução não é completamente segura; no entanto, ajuda na proteção contra bots comuns.

O código completo pode ser visualizado no github . Ficarei feliz em ouvir recomendações e comentários.

All Articles