Логин пользователя в PHP: от простого к сложному
Основы логина пользователя на PHP
При разработке системы управления пользователями важнейшим компонентом является безопасная аутентификация. Рассмотрим различные способы реализации логина с акцентом на современные и безопасные подходы.
Безопасный логин с использованием PDO и password_hash
Как сделать логин с хешированием паролей и защитой от SQL-инъекций?
Этот метод считается основным для веб-приложений. Используются подготовленные запросы PDO и функции password_hash/password_verify.
Пример регистрации (хеширование пароля):
// registration.php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->prepare('INSERT INTO users (email, password) VALUES (:email, :password)');
$userEmail = $_POST['email'];
$userPassword = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt->execute(['email' => $userEmail, 'password' => $userPassword]);
Пример логина (проверка пароля):
// login.php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $_POST['email']]);
$user = $stmt->fetch();
if ($user && password_verify($_POST['password'], $user['password'])) {
session_start();
$_SESSION['user_id'] = $user['id'];
// регенерация сессии для защиты
session_regenerate_id();
// перенаправление
header('Location: profile.php');
exit;
} else {
echo 'Неверный email или пароль.';
}
Пояснение шагов: Хеш пароля сохраняется в БД, при проверке используется password_verify, которая автоматически определяет алгоритм. PDO предотвращает SQL-инъекции. Регенерация сессии после входа снижает риск фиксации сессии.
Возможные проблемы: Неправильная настройка PDO (отсутствие исключений), утечка хеша пароля при ошибке в коде, забытая регенерация сессии, отсутствие проверки на CSRF в форме логина. Рекомендуется также использовать HTTPS.
Вариант 1: Логин без хеширования (хранение пароля в открытом виде)
Как сделать логин, если пароль хранится как есть?
Этот подход категорически не рекомендуется из-за отсутствия безопасности. При компрометации базы данных все пароли становятся известны. Пример кода:
$query = "SELECT * FROM users WHERE email='".$_POST['email']."' AND password='".$_POST['password']."'";
$result = mysqli_query($conn, $query);
if (mysqli_num_rows($result) > 0) { /* успех */ }
Проблемы: SQL-инъекции, хранение паролей в открытом виде, отсутствие защиты от перебора.
Вариант 2: Логин с устаревшим хешированием (md5, sha1)
Как использовать md5 для хранения паролей?
Ранее применялся md5, но сейчас он считается небезопасным из-за коллизий и радужных таблиц. Пример:
$password = md5($_POST['password'] . 'salt');
$query = "SELECT * FROM users WHERE password='$password'";
Проблемы: Уязвимость к коллизиям, быстрое вычисление хеша, необходимость соли. Современные алгоритмы (bcrypt, argon2) предпочтительнее.
Вариант 3: Логин с использованием JWT (JSON Web Token) для API
Как реализовать бесcессионный логин с выдачей токена?
JWT подходит для REST API. После проверки пароля сервер генерирует токен и возвращает его клиенту. Пример на PHP с библиотекой firebase/php-jwt:
require_once 'vendor/autoload.php';
use Firebase\JWT\JWT;
$secretKey = 'your-secret-key';
$payload = [
'user_id' => $user['id'],
'exp' => time() + 3600
];
$token = JWT::encode($payload, $secretKey, 'HS256');
echo json_encode(['token' => $token]);
Клиент отправляет токен в заголовке Authorization. Сервер проверяет подпись и срок действия.
Проблемы: Управление секретами, отзыв токенов (черные списки), уязвимость при утечке секрета, необходимость HTTPS. Также JWT не может быть отозван мгновенно, если не использовать черные списки.
Вариант 4: Логин через OAuth (Google, Facebook)
Как добавить вход с использованием стороннего провайдера?
OAuth 2.0 позволяет пользователям входить через учетные записи Google или Facebook. Реализация требует регистрации приложения у провайдера. Пример с Google Client API:
$client = new Google\Client();
$client->setClientId('CLIENT_ID');
$client->setClientSecret('CLIENT_SECRET');
$client->setRedirectUri('http://example.com/callback');
$client->addScope('email');
// перенаправление на Google
$authUrl = $client->createAuthUrl();
header('Location: ' . $authUrl);
После возврата код обменивается на токен, получаются данные пользователя.
Проблемы: Сложность настройки, необходимость поддержки нескольких провайдеров, безопасное хранение секретов, возможное отсутствие email у пользователя.
Расширенные примеры реализации логина
Пример 1: Безопасный логин с CSRF-защитой и валидацией
Полный скрипт login.php с генерацией и проверкой CSRF-токена, фильтрацией входных данных, использованием PDO и password_verify.
<?
session_start();
// Генерация CSRF-токена при загрузке формы
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['csrf_token'];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Проверка CSRF
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
die('Ошибка CSRF');
}
// Фильтрация
$email = filter_var($_POST['email'], FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
if (!$email || empty($password)) {
$error = 'Введите корректный email и пароль.';
} else {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) {
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Неверный email или пароль.';
}
}
}
?>
<form method='post'>
<input type='hidden' name='csrf_token' value='<?= htmlspecialchars($csrfToken) ?>'>
<label>Email: <input type='email' name='email' required></label><br>
<label>Пароль: <input type='password' name='password' required></label><br>
<button type='submit'>Войти</button>
<?php if (isset($error)): ?><p><?= htmlspecialchars($error) ?></p><?php endif; ?>
</form>
Результат: При успешном входе пользователь перенаправляется на dashboard.php. При ошибке отображается сообщение. Форма защищена от CSRF-атак.
Пример 2: Функция «Запомнить меня» (Remember Me)
Расширение функциональности с помощью долгосрочной куки и токена, хранящегося в БД. Пользователь может не вводить пароль при повторном визите в течение заданного времени (например, 30 дней).
// login.php при установке флажка 'remember'
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
if (!empty($_POST['remember'])) {
$token = bin2hex(random_bytes(32));
$hashedToken = hash('sha256', $token);
// сохраняем в отдельную таблицу remember_tokens
$stmt = $pdo->prepare('INSERT INTO remember_tokens (user_id, token_hash, expires_at) VALUES (:uid, :hash, :exp)');
$stmt->execute([
'uid' => $user['id'],
'hash' => $hashedToken,
'exp' => date('Y-m-d H:i:s', time() + 30*24*3600)
]);
setcookie('remember_token', $token, time() + 30*24*3600, '/', '', true, true);
}
header('Location: dashboard.php');
exit;
}
// Файл check_remember.php при заходе без сессии
if (empty($_SESSION['user_id']) && !empty($_COOKIE['remember_token'])) {
$token = $_COOKIE['remember_token'];
$hashedToken = hash('sha256', $token);
$stmt = $pdo->prepare('SELECT * FROM remember_tokens WHERE token_hash = :hash AND expires_at > NOW()');
$stmt->execute(['hash' => $hashedToken]);
$record = $stmt->fetch();
if ($record) {
$_SESSION['user_id'] = $record['user_id'];
// удалить старый токен? лучше сгенерировать новый
}
}
Результат: При выборе опции «Запомнить меня» и успешном входе устанавливается кука с токеном, по которой сессия восстанавливается при следующем визите. Токен в БД хранится в виде хеша, что безопасно при утечке.