Реализация смены пароля через HTTP на PHP

Раздел: Безопасность 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.

Смена пароля через HTTP в PHP - comments

En
Http change password php (php)