Введение
У 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;
}
Надеюсь, кому-то пригодиться в работе, также буду рад комментариям и вопросам.
Комментарии (5)
Mnemonik
12.10.2021 14:01+4Мы так сделали в первой версии, оказалось что против ботов это не то чтобы супер эффективно, - боты зачастую заточены на решение капчи второй версии, причём делают это как-то через фермы с живыми людьми, то есть где-то живые люди решают капчи по публичным ключам с сайта, и производят коды решения. и в итоге скрипты просто игнорируют третью капчу и херачат сразу запросы к апи с решениями второй капчи (сразу с правильными ключами в параметрах). Я смотрю эти скрипты подвержены той же проблеме - достаточно передать им решение второй капчи и всё будет тип топ.
В итоге наша финальная версия имеет промежуточный стейдж - после того как бэк решает что третья капча так себе была решена, бэк возвращает запрос на рендер второй капчи с nonce и подписью nonce + дата + решение третьей капчи. и решение второй капчи принимается только если вместе с ним поставляется так же решение третьей плюс nonce плюс дата плюс подпись из ответа на третью. И если дата разъезжается минуты на две - машем ручкой.
Так удалось логически прицепить не решивших третью капчу к решающим вторую и как показала практика это сильно затруднило жизнь всяким умельцам которые покупают на сайтах "решение второй капчи 1 доллар за 10000", потому что это уже просто так не заинтегрировать туда (хотя и можно конечно).Конечно боты всё равно регистрируются, но теперь судя по запросам из логов это живые люди которые решают капчи в браузерах. Потому что запрашиваются целиком страницы со стилями и скриптами. Но хотя бы не бесконечный поток запросов прямо к api с корректными решенями второй капчи.
anonymous
ifap
А слепые Google не особо интересны — покупательская способность в среднем ниже, CTR от баннеров и контекстной — никакущий, так что перетопчутся.
berryllium Автор
Думаю, невидимая капча им как раз в помощь - они же перемещаются в браузере по Tab, заполняют поля с клавиатуры, скрипт гугла наверняка это дело анализирует и распознает человека