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

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

Тем не менее, столкнувшись с необходимостью установить капчу в форму авторизации для очередного проекта, а так же после нескольких часов возни с сервисом 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, весь код довольно лаконичен, и полностью находится под нашим контролем.
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 22

    0
    А где посмотреть на примеры капчи? Может я плохо ищу, но на офф сайте скриншотов не вижу.
      –2
      Проект только начал — можно посмотреть тут
        +2
        > Проект только начал

        Неужели капча — это самая нужная или самая интересная часть вашего проекта, что вы начали с нее?
          –2
          Нет конечно, но сделав ядро, решил повесить заглушку, которая предусматривает авторизацию, и соответственно капчу.
      0
      Если просто зайти на страницу, то логотип яндекса будет на русском, но щёлкнув по капче (чтобы обновить её) логотип яндекса пишется на английском.
        0
        Вероятно где-то в роутере ошибка, спасибо что обратили внимание.
        0
        Смотрел код рекапчи, не видел никаких iframe — что я делаю не так?
          0
          у меня есть, объясните, лучше, что я делаю не так? :)
          api.monosnap.com/image/download?id=Bsvk615PSL9gZwcXZ24yDgQDx

          код для питона взял из оффдоков developers.google.com/recaptcha/intro?hl=ru
            0
            Хм, странно.

            Предлагаю в таком случае к решению задачи отрисовки капчи идти не через Python, а другим путём (каким я шёл):

            1. Подключить джаваскрипт рекапчи — http://www.google.com/recaptcha/api/js/recaptcha_ajax.js.
               
            2. Джаваскриптом вызвать создание рекапчи где нужно. У меня это делается вот как:

              Recaptcha.create(
                 'здесь публичный ключ лежит',
                 'здесь ID контейнера лежит',
                 {
                    theme: 'clean',
                    lang: 'ru'
                 }
              );
              

            Никакого iframe не появляется.

            Может быть, это потому, что JavaScript API в нём не нуждается.

            Может быть, это потому, что тема — clean (а у Вас в примере — red).

            Проверяйте.
          +1
          Это же совершенно обычная задача для разработчика. Зачем вы притащили ее сюда?
            0
            Думал не отвечать и всё же. Я с вами согласен, задача действительно типовая и не несёт никаких особенных «инноваций». Тем не менее, принимая решение о написании этой статьи, я был практически уверен, что найдутся люди которым будет интересно, и оказался совершенно прав.
              +1
              Да ладно, пара плюсов это не совершенная правота.
            0
            Я думаю главное отличие и жирный плюс Яндекс капчи в том, что проверочный код можно настроить на вывод русских букв. А это уже
            1) Пользователю не придется переключать раскладку в большинстве случаев
            2) Очень сильно снижается количество спама, т.к. не все программы могут распознать такую капчу. Ведь у индусов русской раскладки клавиатуры нет :))
              0
              сервисы антикапч вполне себе легко обходят русские буквы. в тот же pixodrom.com если передавать параметр is_russian=1 то индусам такие капчи не будут отправляться и за 0.9$ тысячу таких картинок распознают.
                0
                Сервисы да, но не все программы могут распределять капчи в зависимости от содержимого.
                  0
                  Обычно капчи не меняются, поэтому их тип настраивают заранее.
              +1
              $passworld
              

              Передать мир? Куда? Зачем?
                +1
                hash("whirlpool","super".$password."orgy")

                Лучше бы обратили внимание как интересно шифруется пароль xD, я так надеялся что кто-то заметит…
                0
                Набросились на человека как коршуны. Очень даже нормальная статья, и ДА, очень даже полезная. В частности мне! Я не программист, но иногда бывает нужно решить какие то не большие задачи или создать не большой веб проект и такая статья весьма облегчает мне жизнь!!! За что и спасибо автору.
                PS. Как будет развиваться Яндекс API.Чистый Веб? не только ведь ради капчи ее создавали..?) Хотелось бы узнать каких фичей ждать)
                  0
                  У вас ужасное решение. Пользователь ожидает, пока сервер обратится к яндексу. Не делайте так.
                  В свое время столкнувшись с такой задачей, заюзал готовое решение и в проекте дописал 2 строчки.
                    0
                    Тоже хороший вариант, но специально посмотрел сейчас код recaptchalib.php, с удивлением обнаружил что она не генерирует картинку на сервере приложения, а так же обращается за картинкой к Гуглу. По Вашим словам «ужасное решение» используют тысячи сайтов.
                      0
                      И сделано это по понятным причинам — для генерации изображения нужно расширение GD или ему подобное. В свою очередь API (в данном случае API Google) должно иметь минимальные требования к серверу приложения, а расширения для работы с графикой ввиду того, что они достаточно требовательны к ресурсам сервера, есть не везде.

                    Only users with full accounts can post comments. Log in, please.