Реализация смены пароля через HTTP на PHP
Основные принципы смены пароля через HTTP в PHP
Наиболее эффективное решение: форма с POST-запросом, проверка старого пароля, хэширование нового, защита от CSRF
Для безопасной смены пароля используется HTML-форма с методом POST, которая отправляет данные на сервер. Сервер проверяет текущий пароль пользователя, затем хэширует новый пароль с помощью password_hash() и обновляет запись в базе данных. Обязательно используется CSRF-токен для защиты от межсайтовой подделки запросов.
// Пример PHP кода обработки смены пароля
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$current = $_POST['current_password'] ?? '';
$new = $_POST['new_password'] ?? '';
$confirm = $_POST['confirm_password'] ?? '';
$token = $_POST['csrf_token'] ?? '';
// Проверка CSRF токена
if (!hash_equals($_SESSION['csrf_token'], $token)) {
$_SESSION['error'] = 'Недействительный токен';
header('Location: change_password.php');
exit;
}
// Проверка совпадения нового пароля
if ($new !== $confirm) {
$_SESSION['error'] = 'Новый пароль и подтверждение не совпадают';
header('Location: change_password.php');
exit;
}
// Получение текущего хэша пользователя из БД (предполагается PDO)
require 'db.php';
$stmt = $pdo->prepare('SELECT password_hash FROM users WHERE id = ?');
$stmt->execute([$_SESSION['user_id']]);
$row = $stmt->fetch();
// Проверка старого пароля
if (!password_verify($current, $row['password_hash'])) {
$_SESSION['error'] = 'Неверный текущий пароль';
header('Location: change_password.php');
exit;
}
// Хэширование нового пароля
$newHash = password_hash($new, PASSWORD_DEFAULT);
// Обновление в БД
$update = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
$update->execute([$newHash, $_SESSION['user_id']]);
$_SESSION['success'] = 'Пароль успешно изменён';
header('Location: profile.php');
exit;
}
Форма должна содержать скрытое поле с CSRF-токеном, который генерируется в сессии.
Типичные ошибки:
- Использование GET-запроса – пароль может попасть в логи сервера и историю браузера.
- Отсутствие проверки CSRF – уязвимость к атакам.
- Хранение пароля в открытом виде или использование устаревших функций (md5, sha1).
- Недостаточная проверка силы нового пароля.
Как сделать смену пароля через GET-запрос?
Технически возможно передавать параметры в строке запроса, но такой подход крайне небезопасен. Пример:
// Не рекомендуется
// URL: /change.php?current=old&new=new&confirm=new
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$current = $_GET['current'] ?? '';
// ...
}
Проблемы: пароли видны в адресной строке, кешируются браузером, сохраняются в истории. Решение – всегда использовать POST.
Как обеспечить проверку совпадения нового пароля?
Добавить поле подтверждения пароля и сравнить на сервере. Дополнительно можно проверять минимальную длину и сложность.
$new = $_POST['new_password'];
$confirm = $_POST['confirm_password'];
if ($new !== $confirm) {
// ошибка
}
// Проверка сложности
if (strlen($new) < 8 || !preg_match('/[A-Z]/', $new)) {
// пароль слишком простой
}
Ошибка: не учитываются различия в кодировках или пробелах. Решение: использовать trim() до сравнения. Также стоит валидировать на стороне клиента для удобства, но серверная проверка обязательна.
Как реализовать смену пароля через email-ссылку?
Пользователь запрашивает сброс пароля, на email отправляется одноразовый токен. По ссылке открывается форма для установки нового пароля.
// Генерация токена
$token = bin2hex(random_bytes(32));
// Сохранение в БД с привязкой к пользователю и сроком действия
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$stmt = $pdo->prepare('INSERT INTO password_resets (user_id, token, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$userId, $token, $expires]);
// Отправка email со ссылкой: https://example.com/reset.php?token=...
Типичная ошибка: не ограничивать срок действия токена, не удалять использованные токены, уязвимость к подбору токена (если используется слабая энтропия). Решение: использовать random_bytes(), ограничить время жизни, однократное использование.
Расширенные примеры реализации смены пароля
Пример 1: Полный скрипт с CSRF-защитой, проверкой сложности и транзакцией
<?
session_start();
require 'db.php';
$error = '';
$success = '';
// Генерация CSRF токена
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$csrf = $_POST['csrf_token'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $csrf)) {
$error = 'Ошибка валидации токена.';
} else {
$current = $_POST['current_password'] ?? '';
$new = $_POST['new_password'] ?? '';
$confirm = $_POST['confirm_password'] ?? '';
if ($new !== $confirm) {
$error = 'Пароли не совпадают.';
} elseif (strlen($new) < 10 || !preg_match('/[A-Za-z]/', $new) || !preg_match('/[0-9]/', $new)) {
$error = 'Новый пароль должен быть не менее 10 символов, содержать буквы и цифры.';
} else {
try {
$pdo->beginTransaction();
$stmt = $pdo->prepare('SELECT password_hash FROM users WHERE id = ? FOR UPDATE');
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if (!$user || !password_verify($current, $user['password_hash'])) {
$error = 'Текущий пароль неверен.';
} else {
$newHash = password_hash($new, PASSWORD_BCRYPT, ['cost' => 12]);
$update = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
$update->execute([$newHash, $_SESSION['user_id']]);
$pdo->commit();
$success = 'Пароль успешно изменён.';
// Сброс CSRF токена после успешной операции
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
} catch (Exception $e) {
$pdo->rollBack();
$error = 'Произошла ошибка базы данных.';
}
}
}
}
?>
<? if ($error): ?>
<?= htmlspecialchars($error) ?>
<? endif; ?>
<? if ($success): ?>
<?= htmlspecialchars($success) ?>
<? endif; ?>
Результат: при успешной смене пароль обновляется в БД, пользователь видит сообщение об успехе. При ошибках отображается соответствующее предупреждение.
Пример 2: Сброс пароля через email с одноразовым токеном
<?
// ... ранее сгенерированный токен сохранён в БД (см. content)
// Файл reset_password.php (обработчик по ссылке)
session_start();
require 'db.php';
$error = '';
$success = '';
$token = $_GET['token'] ?? '';
if (!$token) {
die('Некорректная ссылка.');
}
// Поиск токена в БД
$stmt = $pdo->prepare('SELECT user_id, expires_at FROM password_resets WHERE token = ? AND used = 0');
$stmt->execute([$token]);
$reset = $stmt->fetch();
if (!$reset || strtotime($reset['expires_at']) < time()) {
$error = 'Срок действия ссылки истёк или токен недействителен.';
} else {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$new = $_POST['new_password'] ?? '';
$confirm = $_POST['confirm_password'] ?? '';
if ($new !== $confirm) {
$error = 'Пароли не совпадают.';
} else {
// Хэширование и обновление
$newHash = password_hash($new, PASSWORD_DEFAULT);
$pdo->beginTransaction();
$updateUser = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
$updateUser->execute([$newHash, $reset['user_id']]);
$invalidateToken = $pdo->prepare('UPDATE password_resets SET used = 1 WHERE token = ?');
$invalidateToken->execute([$token]);
$pdo->commit();
$success = 'Пароль восстановлен. Теперь можно войти.';
}
}
}
?>
<? if ($error): ?>
<?= htmlspecialchars($error) ?>
<? endif; ?>
<? if ($success): ?>
<?= htmlspecialchars($success) ?>
<? endif; ?>
Результат: после успешной смены пользователь перенаправляется на страницу входа. Токен становится недействительным.
Пример 3: Использование PDO и prepared statements для защиты от SQL-инъекций
// db.php – пример подключения с PDO
$host = 'localhost';
$dbname = 'test';
$user = 'root';
$pass = '';
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Ошибка подключения к базе данных.');
}
// Пример запроса с параметрами
$stmt = $pdo->prepare('SELECT password_hash FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch();
Результат: выполнение запроса безопасно, параметры экранируются PDO.