В этой статье мы собираемся реализовать компонент React для рендеринга элемента Google ReCaptcha V2 и службу PHP / Symfony для проверки правильности заполнения ReCaptcha пользователем.
Есть несколько тонкостей, касающихся интеграции ReCaptcha в компонент React, поэтому я написал эту статью.

Создайте компонент ReCaptcha

Вставьте первый скрипт Google в свой основной HTML-файл:

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- ... -->
    <script src="https://www.google.com/recaptcha/api.js"></script>
    <!-- ... -->
  </head>
  <body><!-- ... --></body>
</html>

Создайте специальный компонент для рендеринга элемента ReCaptcha:

import React, { Component } from 'react';  
import PropTypes from 'prop-types';

class ReCaptcha extends Component {  
  constructor(props) {
    super(props);

    this.loadRecaptcha = this.loadRecaptcha.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  componentDidMount() {
    if (document.readyState === 'complete') {
      // Window was already loaded (the component is rendered later on)
      // ReCaptacha can be safely loaded
      this.loadRecaptcha();
    } else {
      // Wait that the window gets loaded to load the ReCaptcha
      window.onload = this.loadRecaptcha;
    }
  }

  getValue() {
    window.grecaptcha.getResponse(this.recatchaElt);
  }

  loadRecaptcha() {
    const { id, apiKey, theme } = this.props;

    this.recatchaElt = window.grecaptcha.render(id, {
      sitekey: apiKey,
      theme,
      callback: this.handleChange,
    });
  }

  handleChange() {
    const { onChange } = this.props;

    onChange(this.getValue());
  }

  render() {
    const { id } = this.props;

    return (
      <div id={id} />
    );
  }
}

ReCaptcha.propTypes = {  
  id: PropTypes.string.isRequired,
  apiKey: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  theme: PropTypes.oneOf(['dark', 'light']),
};

ReCaptcha.defaultProps = {  
  theme: 'light',
};

export default ReCaptcha;

Как видите, я определил для компонента 3 свойства:

  • id (обязательно): идентификатор элемента ReCaptcha.
  • apiKey (обязательно): ключ API, который необходимо создать на веб-сайте Google с вашей учетной записью.
  • onChange (обязательно): метод, который будет обрабатывать изменение статуса ReCaptcha в родительском компоненте.
  • theme (по умолчанию: light): тема ReCaptcha (dark или light).

💡 Совет: если вы используете ESLint для проверки качества кода, вы должны добавить некоторую конфигурацию, чтобы избежать предупреждений об использовании глобальных переменных document и window. Это моя конфигурация в моем .eslintrc.js файле в корне моего проекта:

module.exports = {  
    "extends": "airbnb",
    "rules": {
        "react/jsx-filename-extension": "off",
    },
    "env": {
        // 👇This line avoids warnings for browser global variables
        "browser": true,
    }
};

Затем вы можете использовать компонент в своем родительском компоненте:

import React, { Component } from 'react';
import ReCaptcha from './ReCaptcha';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      captchaResponse: null,
    };
  }
  render() {
    return (
      <div>
        <ReCaptcha
          id="recaptcha"
          apiKey="YOUR_API_KEY"
          onChange={(response) => { this.setState({ captchaResponse: response }); }}
        />
      </div>
    );
  }
}
export default App;

Бэкэнд-проверка с использованием PHP / Symfony

Давайте создадим службу, которая будет проверять значение ReCaptcha (captchaResponse в состоянии примера компонента, использующего ReCaptcha компонент выше).

Этот сервис разработан для Symfony, но здорово то, что сервисы Symfony являются чистыми объектами PHP и, следовательно, не зависят от фреймворка, поэтому эти классы могут использоваться в любом проекте PHP.

Интерфейс проверки капчи

Давайте сначала определим интерфейс средства проверки Captcha, чтобы в будущем можно было легко переключиться на любую другую систему Captcha:

<?php
namespace App\Captcha;
use GuzzleHttp\Client;
interface CaptchaCheckerInterface
{
    /**
     * Checks captcha response
     *
     * @param string $captchaResponse
     * @return bool
     */
    public function check(string $captchaResponse): bool;
}

Проверка капчи для ReCaptcha

Сначала установите Guzzle:

composer require guzzlehttp/guzzle:~6.0

Определите средство проверки ReCaptcha:

<?php
namespace App\Captcha;
use GuzzleHttp\Client;
class GoogleReCaptchaChecker implements CaptchaCheckerInterface
{
    protected $secret;
    public function __construct(string $secret)
    {
        $this->secret = $secret;
    }
    /**
     * {@inheritDoc}
     */
    public function check(string $captchaValue): bool
    {
        $response = $this->getCaptchaResponse($captchaValue);
        // Better checks could be done here
        if ($data && isset($data['success']) && true === $data['success']) {
            return true;
        }
        return false;
    }
    
    private function getCaptchaResponse($captchaValue): array
    {
        $response = $this->getClient()->request(
            'POST',
            'recaptcha/api/siteverify',
            [
                'form_params' => [
                    'secret'   => $this->secret,
                    'response' => $captchaValue,
                ],
            ]
        );
        return json_decode($response->getBody(), true);
    }
    private function getClient(): Client
    {
        return new Client([
            'base_uri' => 'https://www.google.com',
        ]);
    }
}

Если вы используете Symfony, определите свой секрет ReCaptcha, предоставленный Google, в качестве переменной среды и автоматически настройте соответствующую службу:

# config/services.yaml
services:
    App\Service\CaptchaChecker:
        arguments:
            $secret: '%env(GOOGLE_RECAPTCHA_SECRET)%'

Воспользуйтесь сервисом для проверки значения ответа Captcha

Теперь вы можете использовать сервис где угодно, например, в контроллере:

<?php
namespace App\Controller;
use App\Captcha\CaptchaCheckerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class CaptchaController extends Controller
{
    /**
     * @Route("/captcha/check")
     */
    public function check(Request $request, CaptchaCheckerInterface $captchaChecker)
    {
        $isCaptchaValid = $captchaChecker->check(
            $request->request->get('captcha')
        );
        // ...
    }
}

Первоначально опубликовано на сайте blog.michaelperrin.fr 27 сентября 2018 г.