Создание простой капчи на PHP

Статья, в которой разберём как написать простую Captcha на PHP. При этом рассмотрим как вариант с использованием сессии, так и куки.
Что такое Captcha?
Captcha (капча) – это некий тест, который человек решает очень легко, а робот – нет (научить компьютер решать его крайне сложно и затруднительно).
Другими словами, основная цель капчи – это определить кем является пользователь: человеком или роботом.
Используется Captcha на сайте для защиты от спама и повышенной нагрузки, которые создают роботы. Капчу можно очень часто встретить в формах регистрации, входа, отправки сообщений, при скачивании файлов и многих других местах.
В большинстве случаев капча отображается как некоторый искаженный или наложенный на фон текст, который посетителю сайта необходимо разобрать и ввести его в некоторое поле. Кроме текста на фоне используется и другие алгоритмы: найти среди множества картинок правильные, собрать пазл, переместить слайдер, нарисовать связь между несколькими картинками и т.д.
Исходные коды капчи
Исходные коды капчи расположены на GitHub: itchief/captcha.
Скриншот формы с капчей:

Процесс разработки капчи представлен в виде следующих этапов:
- верстка формы;
- создания файла «captcha.php» для генерация кода капчи и изображения;
- написание обработчика для формы (файл «process-form.php»);
- написание JavaScript для отправки формы на сервер через AJAX и обработки ответа.
Верстка формы
Разработку Captcha начнём с создания формы. Для простоты форма будет состоять из капчи и кнопки отправить:
<!-- Сообщение которое будем показывать при успешной отправки формы -->
<div class="form-result d-none">Форма успешно отправлена!</div>
<!-- Форма -->
<form id="form" action="/assets/php/process-form.php" method="post">
<!-- Капча -->
<div class="captcha">
<div class="captcha__image-reload">
<img class="captcha__image" src="/assets/php/captcha.php" width="132" alt="captcha">
<button type="button" class="captcha__refresh"></button>
</div>
<div class="captcha__group">
<label for="captcha">Код, изображенный на картинке</label>
<input type="text" name="captcha" id="captcha">
<div class="invalid-feedback"></div>
</div>
</div>
<!-- Кнопка "Отправить" -->
<button type="submit">Отправить</button>
</form>
Генерация кода капчи и изображения
Формирование кода капчи и изображения выполним в файле «captcha.php», который поместим в папку «/assets/php»:
<?php
define('USE_SESSION', true);
// 1. Генерируем код капчи
// 1.1. Устанавливаем символы, из которых будет составляться код капчи
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890abcdefghijklmnopqrstuvwxyz';
// 1.2. Количество символов в капче
$length = 6;
// 1.3. Генерируем код
$code = substr(str_shuffle($chars), 0, $length);
if (USE_SESSION) {
// 2a. Используем сессию
session_start();
$_SESSION['captcha'] = crypt($code, '$1$itchief$7');
session_write_close();
} else {
// 2a. Используем куки (время действия 600 секунд)
$value = crypt($code, '$1$itchief$7');
$expires = time() + 600;
setcookie('captcha', $value, $expires, '/', 'test.ru', false, true);
}
// 3. Генерируем изображение
// 3.1. Создаем новое изображение из файла
$image = imagecreatefrompng(__DIR__ . '/files/bg.png');
// 3.2 Устанавливаем размер шрифта в пунктах
$size = 36;
// 3.3. Создаём цвет, который будет использоваться в изображении
$color = imagecolorallocate($image, 66, 182, 66);
// 3.4. Устанавливаем путь к шрифту
$font = __DIR__ . '/files//oswald.ttf';
// 3.5 Задаём угол в градусах
$angle = rand(-10, 10);
// 3.6. Устанавливаем координаты точки для первого символа текста
$x = 56;
$y = 64;
// 3.7. Наносим текст на изображение
imagefttext($image, $size, $angle, $x, $y, $color, $font, $code);
// 3.8 Устанавливаем заголовки
header('Cache-Control: no-store, must-revalidate');
header('Expires: 0');
header('Content-Type: image/png');
// 3.9. Выводим изображение
imagepng($image);
// 3.10. Удаляем изображение
imagedestroy($image);
Генерирование текста капчи выполняется очень просто. Для этого в переменную $chars
помещаются символы, из которых она может состоять. Далее с помощью функции str_shuffle()
эти символы случайным образом перемешиваются и посредством substr
выбирается первые шесть из них.
Сохранении полученной капчи по умолчанию осуществляется в сессионную переменную. Но если хотите в куки, то установите переменной $use_session
значение false
:
define('USE_SESSION', false);
Если используете протокол HTTPS, то установите шестому аргументу значение true:
setcookie('captcha', $value, $expires, '/', 'test.ru', true, true);
Для отправки капчи клиенту создается изображение, имеющее фон «bg.png», на котором с помощью функции imagefttext()
пишется текст капчи.
Скрипт для обновления капчи на форме
Код для обновления капчи при нажатию на кнопку .captcha__refresh
:
// функция для обновления капчи
const refreshCaptcha = (target) => {
const captchaImage = target.closest('.captcha__image-reload').querySelector('.captcha__image');
captchaImage.src = '/assets/php/captcha.php?r=' + new Date().getUTCMilliseconds();
}
// получение кнопки для обновления капчи
const captchaBtn = document.querySelector('.captcha__refresh');
// запуск функции refreshCaptcha при нажатии на кнопку
captchaBtn.addEventListener('click', (e) => refreshCaptcha(e.target));
Добавление обработчика к кнопке выполняется через addEventListener
.
Написание обработчика формы
Для обработки формы создадим файл «process-form.php» в папке «/assets/php/»
В этом файле будем сравнивать текст капчи, который пользователь ввел в форме с тем, который у нас хранится в сессии или куки.
<?php
// 1a
session_start();
$captcha = $_SESSION['captcha'];
unset($_SESSION['captcha']);
session_write_close();
// 1b
//$captcha = $_COOKIE['captcha'];
//unset($_COOKIE['captcha']);
//setcookie('captcha', '', time() - 3600, '/', 'test.ru', false, true);
$result = ['success' => false];
$code = $_POST['captcha'];
if (empty($code)) {
$result['errors'][] = ['captcha', 'Пожалуйста введите код!'];
} else {
$code = crypt(trim($code), '$1$itchief$7');
$result['success'] = $captcha === $code;
if (!$result['success']) {
$result['errors'][] = ['captcha', 'Введенный код не соответствует изображению!'];
}
}
echo json_encode($result);
В качестве результата будем возвращать JSON. В случае успеха:
{success: true}
В противном случае, success
присвоим значение false
, а в errors
поместим ошибки:
{
success: false,
errors: [
['captcha', 'Пожалуйста введите код!']
]
}
По умолчанию этот файл сравнивает капчу со значением, находящимся в сессии. Если в «captcha.php» сохраняете капчу в куки, то здесь необходимо закомментировать секцию 1a и раскомментировать 1b:
// 1a
//session_start();
//$captcha = $_SESSION['captcha'];
//unset($_SESSION['captcha']);
//session_write_close();
// 1b
$captcha = $_COOKIE['captcha'];
unset($_COOKIE['captcha']);
setcookie('captcha', '', time() - 3600, '/', 'test.ru', false, true);
Если используете протокол HTTPS, то замените шестой аргумент на значение true
:
//setcookie('captcha', '', time() - 3600, '/', 'test.ru', true, true);
JavaScript для отправки формы на сервер через AJAX
Код для отправки формы на сервер через AJAX и обработки полученного результата:
const form = document.querySelector('#form');
form.addEventListener('submit', (e) => {
e.preventDefault();
try {
fetch(form.action, {
method: form.method,
credentials: 'same-origin',
body: new FormData(form)
})
.then((response) => {
return response.json();
})
.then((data) => {
document.querySelectorAll('input.is-invalid').forEach((input) => {
input.classList.remove('is-invalid');
input.nextElementSibling.textContent = '';
});
if (!data.success) {
refreshCaptcha(form.querySelector('.captcha__refresh'));
data.errors.forEach(error => {
console.log(error);
const input = form.querySelector(`[name="${error[0]}"]`);
if (input) {
input.classList.add('is-invalid');
input.nextElementSibling.textContent = error[1];
}
})
} else {
form.reset();
form.querySelector('.captcha__refresh').disabled = true;
form.querySelector('[type=submit]').disabled = true;
document.querySelector('.form-result').classList.remove('d-none');
}
});
} catch (error) {
console.error('Ошибка:', error);
}
});
В этом коде отправка данных через AJAX выполняется посредством fetch()
. Получение данных формы с использованием FormData
.
Для отправки и получения cookie посредством fetch()
установим:
credentials: 'same-origin',
Если в success
находится значение false
, то будем помечать поля, которые не прошли валидацию и выводить подсказки:
if (!data.success) {
refreshCaptcha(form.querySelector('.captcha__refresh'));
data.errors.forEach(error => {
console.log(error);
const input = form.querySelector(`[name="${error[0]}"]`);
if (input) {
input.classList.add('is-invalid');
input.nextElementSibling.textContent = error[1];
}
})
}

Если в success
содержится значение true
, то будем очищать поля и выводить сообщение об успешной отправки формы:
form.reset();
form.querySelector('.captcha__refresh').disabled = true;
form.querySelector('[type=submit]').disabled = true;
document.querySelector('.form-result').classList.remove('d-none');

itchief.ru/assets/uploadify/b/1/4/b1494ca8468885107caf44c3887e1c97.png
itchief.ru/assets/uploadify/c/3/8/c38dbfd45fa6a5601caef367e7131705.png
Для работы капчи нужно поставить веб-сервер, т.к. она создаётся на серверной стороне. Наиболее просто установить веб-сервер — это воспользоваться программой Open Server или каким-то другим WAMP решением.
Была ошибка:
Начал разбираться в коде и нашел в чем была проблема.
Нужно было убрать один слеш(/) во всех путях т.е было так:
сделал так:
Ну это пустяки! Картинка заработала! Но при отправке формы всегда выводил ошибку, даже если код верный. Начал разбираться дальше, и решил в файле «captcha.php» удалить эту строчку: И все заработало. Но теперь другой вопрос к автору. Зачем вы сохраняли сессии для того чтобы их потом проверить в файле «process-form.php», и сразу же удаляли? Из за этого и была ошибка, что отправляемый код ни с чем не сравнивался!
И я уверен что не я один с этим столкнулся. Если моя логика не верна, прошу объяснить пожалуйста.
С сессиями ошибка вышла. Хотел сохранить данные сессии и завершить её, а вместо этого написал так, что данные уничтожались. Спасибо, этот момент поправил.
Наконец поняла, как вставлять в html-форму созданное в php изображение.
Для этого необходимо добавить на страницу с формами (для каждого изображения) при запросе капчи GET параметр:
И соответственно изменить название полей input, в которые пользователь будет их вводить:
На сервере при генерации изображения необходимо проверять наличие GET параметра и формировать изображения уже с учётом него:
Для валидации формы на стороне сервера соответственно это учитываем:
Инструкцию по реализации своей капчи для MODX (для указанных выше компонентов) опубликую немного позже (в виде отдельной статьи).
Ещё посмотрите путь в html до «captcha.php».
Спасибо за подробный разбор!
А как можно сделать, чтобы с помощью данной формы происходила регистрация нового пользователя?
Класс пользователей users уже создал и права им определил! И как чтобы информация из указанной формы переносилась в базу?
Начать можно делать так: