Введение
У 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, в котором выполняется такая последовательность действий:
Отправляем ajax запрос с данными формы на сервер
Если на сервере проверка пройдена и данные формы приняты, получаем сообщение об успехе и выводим информацию об этом пользователю
Если произошла ошибка, связанная с версией 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- публичный ключ капчи версии 2theme- цветовая схема капчи, может принимать значения dark / lightcallback- имя функции, которая будет вызвана после проверки капчи версии 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; }
Надеюсь, кому-то пригодиться в работе, также буду рад комментариям и вопросам.
