Прощай, reCAPTCHA! Как я защитил формы входа с помощью бесплатной и невидимой CAPTCHA от Cloudflare

от автора

Привет, Хабр! На связи разработчик Peakline — аналитической платформы для Strava. Сегодня я хочу поделиться опытом внедрения Cloudflare Turnstile в веб-приложение на FastAPI. Это решение позволило мне отказаться от назойливых CAPTCHA, улучшить пользовательский опыт и при этом надежно защитить формы регистрации и входа от ботов.

Боль традиционных CAPTCHA

Каждый, кто хоть раз вводил логин и пароль в интернете, сталкивался с ними: «Выберите все светофоры», «Введите искаженный текст», «Я не робот». Классические CAPTCHA, особенно reCAPTCHA от Google, стали синонимом раздражения. Они не только прерывают пользовательский путь, но и вызывают вопросы о конфиденциальности, собирая огромное количество данных.

Для моего проекта, где важен быстрый и удобный доступ к аналитике, такой барьер был неприемлем. Я искал решение, которое было бы:

  • Эффективным против ботов.

  • Незаметным для легитимных пользователей.

  • Простым в интеграции.

  • Бесплатным.

И я его нашел.

Что такое Cloudflare Turnstile?

Turnstile — это современная альтернатива CAPTCHA от Cloudflare. Его главная фишка — он не заставляет пользователей решать головоломки. Вместо этого Turnstile запускает в браузере небольшой набор неинтрузивных JavaScript-тестов, которые собирают данные о поведении и окружении браузера. Эти сигналы отправляются на сервер Cloudflare, где умные алгоритмы определяют, человек перед ними или бот.

Ключевые преимущества:

  • Невидимость для пользователя: В большинстве случаев проверка проходит в фоновом режиме за доли секунды. Никаких кликов и картинок.

  • Конфиденциальность: Turnstile не использует cookie и не отслеживает пользователей между сайтами для построения рекламных профилей, в отличие от reCAPTCHA.

  • Простота интеграции: Добавить Turnstile на сайт можно буквально за 5 минут.

  • Бесплатность: Сервис полностью бесплатен для неограниченного числа проверок.

Шаг 1: Интеграция на фронтенде

Для начала нужно получить ключи (Site Key и Secret Key) в панели управления Cloudflare. Это бесплатно, даже если ваш сайт не использует другие сервисы Cloudflare.

Теперь добавим виджет на страницы регистрации (register.html) и входа (email_login.html). Я использую шаблонизатор Jinja2, но логика будет аналогична для любого фреймворка.

1. Подключаем скрипт. В секцию <head> добавляем скрипт Turnstile.

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

2. Добавляем виджет в форму. Внутри тега <form> размещаем div-элемент, который и будет нашим виджетом.

<form id="login-form">     <!-- Поля для email и пароля -->     <div class="form-group">         <label for="email" class="form-label">Email</label>         <input type="email" id="email" name="email" class="form-input" required>     </div>     <div class="form-group">         <label for="password" class="form-label">Пароль</label>         <input type="password" id="password" name="password" class="form-input" required>     </div>      <!-- Вот и сама капча! -->     <div class="cf-turnstile"          data-sitekey="{{ TURNSTILE_SITE_KEY }}"          data-theme="auto">     </div>      <button type="submit" class="btn btn-primary">Войти</button> </form>

Обратите внимание на атрибут data-sitekey. Я передаю его из бэкенда как переменную окружения, чтобы не «зашивать» ключ прямо в HTML. data-theme="auto" автоматически подстроит внешний вид виджета под светлую или темную тему вашего сайта.

Когда пользователь отправляет форму, Turnstile автоматически добавляет в данные формы скрытое поле cf-turnstile-response. Мне нужно только извлечь его с помощью JavaScript и отправить на бэкенд.

document.getElementById('login-form').addEventListener('submit', async (e) =&gt; {     e.preventDefault();      const formData = new FormData(e.target);     const data = {         email: formData.get('email'),         password: formData.get('password'),         // Получаем токен от виджета         "cf-turnstile-response": formData.get('cf-turnstile-response')     };      // Отправляем данные на наш API     await fetch('/api/auth/login', {         method: 'POST',         headers: { 'Content-Type': 'application/json' },         body: JSON.stringify(data)     }); });

Фронтенд готов!

Шаг 2: Валидация на бэкенде (FastAPI)

Самое интересное происходит на сервере. Мне нужно взять полученный токен cf-turnstile-response и отправить его в Cloudflare для проверки.

1. Модели Pydantic. Я использую Pydantic для валидации входящих данных. Добавим поле для токена в модели входа и регистрации.

from pydantic import BaseModel, Field  class EmailLogin(BaseModel):     email: str     password: str     cf_turnstile_response: str = Field(..., alias="cf-turnstile-response")  class EmailRegistration(BaseModel):     email: str     password: str     # ... другие поля     cf_turnstile_response: str = Field(..., alias="cf-turnstile-response") 

2. Асинхронная функция проверки. Напишем простую асинхронную функцию, которая будет делать POST-запрос к API Cloudflare. Для этого идеально подходит библиотека aiohttp.

import os import aiohttp from loguru import logger # Я использую loguru для логирования  async def verify_turnstile(token: str, ip: str) -&gt; bool:     """Проверяет токен Cloudflare Turnstile."""     secret_key = os.getenv("TURNSTILE_SECRET_KEY")     if not secret_key:         logger.error("TURNSTILE_SECRET_KEY is not set. Skipping validation.")         # В проде здесь лучше возвращать False         return True      payload = {         "secret": secret_key,         "response": token,         "remoteip": ip     }      try:         async with aiohttp.ClientSession() as session:             async with session.post("https://challenges.cloudflare.com/turnstile/v0/siteverify", data=payload) as response:                 if response.status == 200:                     result = await response.json()                     if result.get("success"):                         logger.info(f"Turnstile validation successful for IP: {ip}")                         return True                     else:                         logger.warning(f"Turnstile validation failed for IP: {ip}. Errors: {result.get('error-codes')}")                         return False                 else:                     logger.error(f"Failed to verify Turnstile token. Status: {response.status}")                     return False     except Exception as e:         logger.exception(f"An exception occurred during Turnstile verification: {e}")         return False 

3. Интеграция в эндпоинты. Теперь вызовем нашу функцию в самом начале эндпоинтов регистрации и входа.

from fastapi import FastAPI, Request, HTTPException  app = FastAPI()  @app.post("/api/auth/login") async def login_email_user(request: Request, login_data: EmailLogin):     """Вход пользователя по email и паролю с проверкой Turnstile."""      # Первым делом — проверка CAPTCHA!     if not await verify_turnstile(login_data.cf_turnstile_response, request.client.host):         raise HTTPException(status_code=403, detail="Invalid CAPTCHA. Please try again.")      # ... остальная логика входа ...      return {"status": "success"}  @app.post("/api/auth/register") async def register_email_user(request: Request, registration: EmailRegistration):     """Регистрация нового пользователя с проверкой Turnstile."""          # И здесь тоже     if not await verify_turnstile(registration.cf_turnstile_response, request.client.host):         raise HTTPException(status_code=403, detail="Invalid CAPTCHA. Please try again.")      # ... остальная логика регистрации ...      return {"status": "success"} 

Готово! Теперь формы надежно защищены.

Заключение

Интеграция Cloudflare Turnstile заняла у меня меньше часа, но принесла огромную пользу. Я избавился от раздражающей капчи, сделал процесс входа и регистрации более плавным и не пожертвовал безопасностью. Если вы ищете современное, удобное и бесплатное решение для защиты от ботов — настоятельно рекомендую попробовать Turnstile.

Надеюсь, мой опыт будет вам полезен. Успешных проектов!


ссылка на оригинал статьи https://habr.com/ru/articles/925310/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *