19 мая 2017

Google reCAPTCHA и Drupal 8

Все современные пользователи интернета неоднократно сталкивались с различными captcha. Если говорить кратко, то это аббревиатура от «Completely Automatic Public Turing Test to Tell Computers and Humans Apart». Человек просто и быстро может решить задачу по распознаванию искаженных или пересекающих друг друга букв или цифр. Для того, чтобы подтвердить, что человек действительно человек, а не спам бот.

С течением времени капчи эволюционировали. Простейшие из них предлагают человеку выполнить несложную математическую операцию или ответить на вопрос. Более сложные – распознать слово или слова на изображении. В последние годы практически везде мы встречаем captcha от Google — reCAPTCHA. Есть и так называемая «невидимая» (invisible) reCAPTCHA. Она сама определяет, когда «проверить» пользователя (обычно это распознавание похожих изображений на заданную тему) и не портит внешний вид вашей формы.

Невидимая капча на фронте и как написать обработчик на бекэнде используя Drupal

Для Drupal 8 есть готовый модуль reCAPTCHA, далее мы рассмотрим самостоятельную работу с каптчей, без использования этого модуля.

Процесс работы капчи следующий: со стороны фронта получаем токен, отправляем этот токен на бекэнд. Бекэнд делает POST запрос к Google API с данными: IP-юзера и токен полученный с фронта.

Front-end

Добавляем скрипт капчи (в документации отмечено, что протокол обязательно https):

<script src="https://www.google.com/recaptcha/api.js?hl=ru" async defer></script>

Параметр ?hl=ru нужен чтобы русифицировать капчу.

Внутри формы добавляем разметку капчи:

<div class="g-recaptcha"
  data-sitekey="sitekey"
  data-callback="onSubmit"
  data-size="invisible">
</div>

При отправке формы мы должны вызвать grecaptcha.execute(); (например, в onSubmit). При этом дополнительных действий по встраиванию токена в форму делать не надо, капча все сделает за нас. При сабмите формы у нас появится параметр g-recaptcha-response, в котором содержится токен капчи для проверки на сервере.

Возможен вариант без прямого вызова grecaptcha.execute(), тогда параметры data-sitekey и data-callback прописываются прямо на кнопке сабмита формы. Первый вариант — более универсален.

Back-end

Пример обработки на Drupal 8 (обработка данных из POST запроса):

use GuzzleHttp\Exception\RequestException;

... 
 
// Get ReCaptcha token from front end
$data['google_captcha_token'] = '';
$data['google_captcha_token'] = \Drupal::request()->request->get('g-recaptcha-response');
 
// dev:
\Drupal::logger('my_module')->notice($data['google_captcha_token']);
 
$user_ip = \Drupal::request()->getClientIp();
$secret_key = '<GOOGLE_SECRET_KEY>';
 
// Check (true - spam)
$spam = $this->checkSpamRecaptcha($secret_key, $user_ip, $data['google_captcha_token']);
 
if (!$spam) {
  // ...do stuff here
} 
else {
  // ...spamer!
}

Не забудьте подставить в $secret_key ваше значения ключа. И удалить или изменить строку с логированием.

Функция проверки:

/**
 * Check Google API for ReCaptcha information
 *
 * POST (application/x-www-form-urlencoded) request to
 * https://www.google.com/recaptcha/api/siteverify
 *
 * @param $secret_key
 *  ReCaptcha secret key from Google recaptcha service
 *
 * @param $remoteip
 *  User ip
 *
 * @param $g_recaptcha_response
 *  Special token from front-end
 *
 * @return bool
 *  TRUE - spam, FALSE - human and good man
 */
public function checkSpamRecaptcha($secret_key, $remoteip, $g_recaptcha_response) {
  $spamer = true;
  $client = \Drupal::httpClient();
 
  try {
    // Sending application/x-www-form-urlencoded POST requests requires that you 
    // specify the POST fields as an array in the form_params request options.
    // http://docs.guzzlephp.org/en/latest/quickstart.html#sending-requests
    $body = [
      'form_params' => [
        'secret' => $secret_key,
        'response' => $g_recaptcha_response,
        'remoteip' => $remoteip,
      ],
    ];
    $response = $client->request('POST', 'https://www.google.com/recaptcha/api/siteverify', $body);
    $data = $response->getBody();
 
    $recaptcha = json_decode($data);
 
    if ($recaptcha->success) {
      $spamer = false;
    }
  }
  catch (RequestException $e) {
    \Drupal::logger('my_module')->notice($e->getMessage());
  }
  return $spamer;
}