Pull to refresh

Как подружить капчу Yandex API и AJAX

Reading time 7 min
Views 15K
Здравствуйте, уважаемые коллеги. Споры о том, нужна ли так называемая «капча», приносит ли она реальную пользу в деле борьбы со злобными роботами или только вредит «юзабилити» проекта давно утихли, и каждый, кто так или иначе интересовался этим вопросом сделал для себя соответствующие выводы.

Тем не менее, столкнувшись с необходимостью установить капчу в форму авторизации для очередного проекта, а так же после нескольких часов возни с сервисом reCaptcha, который генерирует на странице тонны мусорного кода, я так и не нашёл готового решения, которое бы устроило меня на сто процентов. Ну что же, если хочешь что-то сделать — сделай это сам.

В данной статье речь пойдёт о превращении простого и удобного API Яндекс — Чистый Веб в полноценную, современную и функциональную капчу. А раз уж мы заговорили о модуле авторизации, то думаю, что уместно будет показать — как наша новая капча работает в связке с модулем.

Итак, нам понадобится:
API Яндекс — Чистый Веб.

Думаю что любителям нативного js больше ничего и не понадобиться, я же использовал библиотеку jQuery

Первым делом обратимся к Yandex API, ведь сперва нам нужно получить искомую капчу. Почитав документацию пишем класс, который собственно её и отдаёт:

class_yandex_capcha.php

class yandexCaptcha {
    static function get() {
        $lang = $_SESSION['lang'];
        if ($lang == "ru") {
            $type = "std";
        }
        else {
            $type = "estd";
        }
        $key = "Ваш API Ключ";
        $xmlResponse = file_get_contents("http://cleanweb-api.yandex.ru/1.0/get-captcha?key=".$key."&type=".$type);
        $xml = simplexml_load_string($xmlResponse);
        return $xml->url;
    }
}


Всё предельно просто — отправляем GET запрос с параметрами:
$key — Яндекс API Ключ, Получить можно тут
$type — тип капчи, которую хотим получить, значения, принимаемые переменной всецело описаны в документации Яндекса.

В моём случае сайт поддерживает несколько языков — поэтому в зависимости от языка выбираем: std — цифры и логотип Яндекса на русском, либо estd — тоже цифры, но с логотипом на английском.

Метод yandexCaptcha::get() теперь возвращает адрес изображения — это и есть наша капча.
Кроме «url картинки капчи» xml запрос метода возвращает ещё один параметр — captcha, но есть маленькая хитрость благодаря которой
параметр captcha можно не хранить, скажем в сессии. Его вообще можно не хранить, почему — объясню немного дальше.

Когда наш класс готов — встраиваем его в модель страницы авторизации на сайте:

model_closed.php

class model_closed extends model {
        function get_data() {
            $root = $_SERVER['DOCUMENT_ROOT'];
            $dataArray['language'] = parse_ini_file($root."/app/languages/".Route::$lang."_closed.ini");
            $dataArray['base_href'] = $_SERVER['HTTP_HOST'];
            require_once($root."/app/core/class/class_yandex_capcha.php");
            $dataArray['capcha_url'] = yandexCaptcha::get();
            return $dataArray;
        }
    }     


Последние три строки метода get_data() отдают картинку капчи представлению:

А вот собственно и представление — closed_view.php

<div class="closedLoginForm">
    <input type="text" class="loginInput" value="<?php echo $data['language']['login']; ?>"><br>
    <input type="password" class="passworldInput" value="<?php echo $data['language']['passworld']; ?>">
    <div id="capcha" title="<?php echo $data['language']['reload_image']; ?>">
        <img class="yandexCapchaImage" src="" alt="<?php echo $data['language']['capcha']; ?>">
    </div>
    <input type="text" class="capchaInput" value="<?php echo $data['language']['capcha']; ?>">
    <div class="loginButton"><div class="loginBottonInner"><?php echo $data['language']['loginBotton']; ?></div></div>
    <div class="loginError"></div>
</div>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/closed.js"></script>
<script type="text/javascript" src="/js/login.js"></script>


Как видно из представления — в html коде нет тегов form, а это значит, что скорее всего для авторизации мы будем использовать AJAX.

В представлении у нас 3 поля: логин, пароль и поле для ввода капчи, сама картинка капчи и кнопка отправить.

Отправлять форму, как уже было сказано ранее, мы будем с помощью AJAX запроса в login.js

Много кода, но именно тут - вся суть
$(document).ready(function(){
    var capchaStartText = $(".capchaInput").val();
    var passworldStartText = $(".passworldInput").val();
    function login() {
        var login = $(".loginInput"), passworld = $(".passworldInput"), capcha = $(".capchaInput"), buttontext = $(".loginBottonInner").html(), captchasrc = $(".yandexCapchaImage").attr("src");
        var pos = captchasrc.indexOf("=");
        var key = captchasrc.substr(pos+1);
        $.ajax({
            type: "POST",
            url: "/app/modules/module_login.php",
            dataType: "json",
            data: {login:login.val(), passworld:passworld.val(), captchaCode:key, captchaValue:capcha.val()},
            beforeSend: function(){
                $(".loginBottonInner").html("...");
            }
        }).done(function(data){
            if (data.captcha == 1 && data.login == 1) {
                location.reload();
            }
            if (data.login == 0) {
                capchaRenew();
                $(".loginBottonInner").html(buttontext);
                login.focus();
                login.select();
                passworld.val(passworldStartText);
                capcha.val(capchaStartText);
                $(".loginError").html(data.login_error);
            }
            if (data.captcha == 0 && data.login == 1) {
                capchaRenew();
                $(".loginBottonInner").html(buttontext);
                capcha.val(capchaStartText);
                capcha.focus();
                $(".loginError").html(data.captcha_error);
            }
        });
    }
    function capchaRenew() {
        $.ajax({
            type: "POST",
            data: {check:"ok"},
            url: "/app/modules/module_capcha_renew.php"
        }).done(function(html) {
            $(".yandexCapchaImage").attr("src",html);
            console.log(html);
        });
    }
    $("#capcha").click(function(){
        capchaRenew();
        $(".capchaInput").focus();
    });
    $(".loginBottonInner").click(function() {
        login();
    });
    $(window).keydown(function(eventObject){
        if ($(".closedLoginForm input").is(":focus") == true) {
            if (eventObject.which == 13) {
                login();
            }
        }
    });
});



Теперь нам нужно проверить капчу.
Для этого из полей нашей формы по клику на кнопку «отправить» либо по кнопке Enter (любим пользователей), хватаем все данные — логин, пароль, набор символов введённый пользователем в поле для ввода капчи, и ещё один параметр, о котором я писал выше — тот самый параметр captcha (смотри описание метода yandexCaptcha::get()).
Дело в том, что этот параметр — ключевой для проверки правильности ввода капчи пользователем изначально присутствует на странице как часть URL картинки капчи. Её отдаёт нам Яндекс всё в том же методе yandexCaptcha::get(). Нам остаётся только «вычленить» параметр captcha из url адреса изображения, что мы и делаем.

Файл module_login.php которому мы передаём данные с помощью AJAX запроса, создаёт экземпляр класса Login — основного класса используемого нами для авторизации пользователей на сайте, вызывает метод siteLogin() который возвращает данные о результате авторизации, проверив перед этим правильность комбинации логин-пароль и, что нас интересует больше всего — правильность ввода капчи.

    echo Login::siteLogin($login, $passworld, $captchaCode, $captchaValue);


Чтобы моя статья, которая и так уже получилась намного длиннее чем я планировал не разрасталась до совершенно громадных размеров, приведу только ту часть метода Login::siteLogin который отвечает за проверку капчи:

class_login.php

Собственно код
class Login {
    static function siteLogin($login, $password, $captchaCode, $captchaValue) {
        $path = $_SERVER['DOCUMENT_ROOT'];
        session_start();
        $langArray = parse_ini_file($path."/app/languages/".$_SESSION['lang']."_login.ini");
        $login = htmlspecialchars($login);
        $password = htmlspecialchars($password);
        $json = Array();
        $json['captcha'] = 0;
        $json['login'] = 0;
        $json['captcha_error'] = $langArray['captcha_error'];
        $json['login_error'] = $langArray['login_error'];
        $key = "Ваш API Ключ";
        $response = file_get_contents("http://cleanweb-api.yandex.ru/1.0/check-captcha?key=".$key."&captcha=".$captchaCode."&value=".$captchaValue);
        if (strpos($response,"<ok")){
            $json['captcha'] = 1;
            unset($json['captcha_error']);
        }
        require_once $path."/app/core/class/class_dbconnect.php";
        $mysqli = dbconnect::connect();
        $sql = "SELECT `id`,`login`,`passworld`,`rights` FROM `users` WHERE `login` = '".$login."'";
        $qr = $mysqli->query($sql);
        $quant = $qr->num_rows;
        if ($quant <> 0) {
            $row = $qr->fetch_assoc();
            if ($row['passworld'] == hash("whirlpool","super".$password."orgy")) {
                $json['login'] = 1;
                unset($json['login_error']);
                if ($json['login'] == 1 and $json['captcha'] == 1) {
                    //Ура! Авторизация прошла успешно    
                }
            }
        }
        return json_encode($json);
    }
}



*Подумал и решил привести весь код, чтобы читателю было понятнее откуда растут ноги.
Суть проверки капчи — снова GET запрос согласно Yandex API;

Соответственно — вели верную комбинацию логин/пароль + капча прошла проверку — Ура! Мы прошли авторизацию.

Дальше, думаю будет наиболее важный момент статьи. Обращаю внимание тех, кто всё ещё читает — всё сводиться к архитектуре приложения.

Предположим пользователь ошибся при вводе капчи, либо просто не может разобрать текст на картинке и хочет её обновить.

Внимание — ему не нужно совершать никаких лишних телодвижений

В случае ошибки — введённые пользователем ранее логин и пароль сохраняются, а картинка капчи автоматически обновляется. А если пользователь решит обновить капчу — ему достаточно просто кликнуть на неё.

Вернёмся к содержимому файла login.js, и рассмотрим функцию capchaRenew()

 function capchaRenew() {
        $.ajax({
            type: "POST",
            data: {check:"ok"},
            url: "/app/modules/module_capcha_renew.php"
        }).done(function(html) {
            $(".yandexCapchaImage").attr("src",html);
        });
    }


Итак — мы просто делаем запрос к модулю module_capcha_renew.php вся функция которого сводится к тому,
чтобы переформировать капчу с помощью того же метода yandexCaptcha::get() к которому мы обращались при загрузке страницы и отдать адрес нового изображения пользователю.

В итоге — имеем полнофункциональную капчу, достаточно дружественную пользователю. А главное, в отличии от того же самого reCapcha — нет никаких iframe, весь код довольно лаконичен, и полностью находится под нашим контролем.
Tags:
Hubs:
+10
Comments 22
Comments Comments 22

Articles