Pull to refresh

Использование Google reCAPTCHA v2 и v3 в одной форме

Reading time4 min
Views26K

Введение

У Google появилась замечательная reCAPTCHA v3. Замечательна она тем, что освобождает пользователя от необходимости тыкать по картинкам в поисках светофоров и пожарных гидрантов, при этом вполне успешно анализирует его действия на сайте и оценивает их по шкале от 0 до 1. Чем выше балл, тем качественней наш бот выше вероятность, что пользователь реальный. Обычно выставляют порог равным 0.5.

А что делать, если хочется добра пользователям сайта, но есть у его владельца некая обеспокоенность, что не пропустит невидимая столь ценного реального пользователя, который может и ведет себя странно и подозрительно, но совсем не бот?

Ответ - последовательно использовать капчу обоих версий - не прошел невидимую проверку - появилась видимая капча!

Что делаем

Тут обозначу основные моменты. Весь код есть по ссылке на github. достаточно переименовать файл config.example.php в config.php и поменять в нем ключи доступа на настоящие.

Для подключения необходимо получить публичные (site key) и приватные (secret key) ключи от обеих версий Google reCAPTCHA на сайте.

Добавляем на страницу скрипт, указав в параметре публичный ключ версии 3

<script src="https://www.google.com/recaptcha/api.js?render=KEY_SITE_V3"></script>

Создаем форму, в которую помимо наших полей добавляем два скрытых для токенов, а также один блок, в который будет добавляться видимая капча. Блоку требуется задать значение атрибута "id".

<input type="hidden" name="captcha_token_v2">
<input type="hidden" name="captcha_token_v3">
<div id="captcha"></div>

Форма у нас будет отправляться по ajax. Пишем обработчик для события подтверждения формы. Тут:

  • form - эта переменная содержит объект формы

  • captcha_key_site_v3 - открытый публичный ключ для капчи 3 версии

  • token - токен, присвоенный пользователю, по нему в дальнейшем будем обращаться в сервис Google и получать те самые баллы


- открытый который проверяет, что определена переменная grecaptcha из библиотеки Google, получает токен и записывает его в скрытое поле, после чего отправляем форму.

form.submit(function (e){
  e.preventDefault()
  if (typeof grecaptcha != 'undefined' && typeof captcha_key_site_v3 != 'undefined') {
    grecaptcha.ready(function () {
      grecaptcha.execute(captcha_key_site_v3, {action: 'submit'}).then(function (token) {
        if(token) {
          form.find('input[name="captcha_token_v3"]').val(token)
          sendForm()
        }
      })
    })
  }
})

Для отправки ипользуется метод sendForm, в котором выполняется такая последовательность действий:

  1. Отправляем ajax запрос с данными формы на сервер

  2. Если на сервере проверка пройдена и данные формы приняты, получаем сообщение об успехе и выводим информацию об этом пользователю

  3. Если произошла ошибка, связанная с версией 3 (не отчечает сервер или недостаточно баллов), то рендерим капчу версии 2

function sendForm() {
                let data = form.serialize()
                $.ajax({
                    type: 'POST',
                    url: form.attr('action'),
                    data: data,
                    dataType: 'json',
                    success: function (response) {
                        if(response.success) {
                            // показываем сообщение об успехе и перезагружаем страницу
                            $('.alert').text(response.text).slideDown()
                            setTimeout(function (){location.reload()}, 4000)
                        } else if(response.error) {
                            // если была ошибка капчи, сбрасываем капчку v2 при наличии
                            if (widgetCaptcha !== false) {
                                grecaptcha.reset(widgetCaptcha)
                            }
                            // если ошибка была в версии v3, показываем видимую капчу v2
                            // widgetCaptcha - идентификатор, т.е. можно рендерить и управлять несколькими штуками
                            if (response.error === 'fall_captcha_v3' && !widgetCaptcha) {
                                widgetCaptcha = grecaptcha.render('captcha', {
                                    'sitekey': captcha_key_site_v2,
                                    'theme': 'dark',
                                    'callback': setTokenV2
                                })
                            }
                        }
                    }
                })
            }

Методу render передаем первым параметром id блока, в который будет помещена капча, вторым - набор параметров:

  • sitekey - публичный ключ капчи версии 2

  • theme - цветовая схема капчи, может принимать значения dark / light

  • callback - имя функции, которая будет вызвана после проверки капчи версии 2

WidgetCaptcha - это переменная, в которой хранится идентификатор капчи, полезно использовать особенно когда на странице несколько форм, для сброса капчи используеся метод grecaptcha.reset(WidgetCaptcha)

В функции-коллбеке setTokenV2 передается токен от второй версии капчи, его мы записываем во второе скрытое поле

function setTokenV2(token) {
  form.find('input[name="captcha_token_v2"]').val(token)
}

В этой же функции можно сразу вызвать sendForm() или предоставить пользователю самостоятельно нажать на кнопку. На этот раз форма улетает уже с двумя токенами, при этом первым проверяется токен версии 2, на всякий случай на стороне сервера по нему тоже получаем инофрмацию от Google, и если все в порядке, возвращаем информацию об успехе в браузер, где и показывем соответстующее сообщение.

Код серверной части в файле ajax.php:

<?php
require_once 'config.php';
session_start();
if(!empty($_POST) && $_POST['sid'] == session_id()) {
    $token_v2 = $_POST['captcha_token_v2'];
    $token_v3 = $_POST['captcha_token_v3'];
    $result = checkCaptcha($token_v2, $token_v3);
    die(json_encode($result, true));
}

function checkCaptcha($token_v2 = false, $token_v3 = false)
{
    // если не передано ни одного токена - возвращаем ошибку
    if (!$token_v3 && !$token_v2) {
        return ['error' => 'fall_captcha'];
    }
    // если дело дошло до капчи второй версии
    elseif ($token_v2) {
        // проверяем информацию по второй версии, если google ответил, что провека успешная - возвращаем успех
        $result = checkCaptchaCurl($token_v2, KEY_SECRET_V2);
        if (!$result['success']) {
            // если проверка провалилась - тоже ошибка
            return ['error' => 'fall_captcha_v2'];
        }
    }
    // если токен второй версии еще не получен, но есть 3, значит проверяем невидимую капчу
    else {
        $result = checkCaptchaCurl($token_v3, KEY_SECRET_V3);
        // проверяем количество очков от 0 до 1. Чем ближе к 1, тем больше вероятности, что это человек
        if ($result['score'] < 1) {
            return ['error' => 'fall_captcha_v3'];
        }
    }
    // возвращаем успех, если проверки пройдены
    $text = 'Your message "' . $_POST['text'] . '" was send to email "' . $_POST['email'] . '" successfully';
    return ['success' => true, 'text' => $text];
}

/**
 * Метод для отправки запроса в google через CURL
 * @param $response
 * @param $secret
 * @return mixed
 */
function checkCaptchaCurl($response, $secret)
{
    $url_data = 'https://www.google.com/recaptcha/api/siteverify' . '?secret=' . $secret . '&response=' . $response . '&remoteip=' . $_SERVER['REMOTE_ADDR'];
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url_data);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $captcha_res = curl_exec($curl);
    curl_close($curl);
    $captcha_res = json_decode($captcha_res, true);
    return $captcha_res;
}

Надеюсь, кому-то пригодиться в работе, также буду рад комментариям и вопросам.

Tags:
Hubs:
Total votes 4: ↑4 and ↓0+4
Comments5

Articles