Разработка защищенного входа: от простого к сложному
Основные подходы к реализации входа на PHP
Наиболее эффективным решением для аутентификации в PHP является использование PDO с подготовленными запросами, хеширование паролей функциями password_hash и password_verify, а также управление сессиями. Это обеспечивает защиту от SQL-инъекций, безопасное хранение паролей и корректную идентификацию пользователя.
// Пример базового скрипта login.php
session_start();
require 'db.php'; // подключение к БД через PDO
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = $_POST['login'] ?? '';
$password = $_POST['password'] ?? '';
// Подготовленный запрос
$stmt = $pdo->prepare('SELECT id, username, password_hash FROM users WHERE username = :username');
$stmt->execute(['username' => $login]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
header('Location: dashboard.php');
exit;
} else {
$error = 'Неверный логин или пароль.';
}
}
?>Admin index php login php (страница входа администратора php)
Здесь используется функция password_verify для проверки пароля, которая автоматически устойчива к атакам по времени. Подготовленный запрос исключает возможность инъекций.
Как реализовать аутентификацию без базы данных?
Для очень простых проектов можно хранить логин и хеш пароля в файле конфигурации. Но такой подход не масштабируется и небезопасен для многих пользователей.
$config = ['admin' => password_hash('secret123', PASSWORD_DEFAULT)];
// проверка: if (isset($config[$login]) && password_verify($password, $config[$login])) { ... }Php code login (код страницы входа php)
Как защитить форму входа от CSRF-атак?
Генерация уникального токена для каждой сессии и проверка его при отправке формы.
// Генерация токена
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// В форме:
// Проверка при POST:
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
die('Недействительный токен CSRF');
}Request login php (запрос на вход php)
Как реализовать функцию 'Запомнить меня'?
Используются долгоживущие токены, хранящиеся в БД и в куках. При входе генерируется токен, сохраняется в БД и устанавливается в куку. При посещении сайта без сессии проверяется кука.
// При входе с отметкой remember
$token = bin2hex(random_bytes(32));
$hashedToken = password_hash($token, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$user['id'], $hashedToken, date('Y-m-d H:i:s', time() + 30*24*3600)]);
setcookie('remember_token', $token, time() + 30*24*3600, '/', '', true, true);
// При последующем визите без сессии проверяем куку
Расширенные примеры реализации аутентификации
Ниже представлены более детальные сценарии с пояснениями каждого шага.
Пример 1: Полная страница входа с валидацией и защитой от перебора
<?php
session_start();
require 'db.php';
if (isset($_SESSION['user_id'])) {
header('Location: dashboard.php');
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = trim($_POST['login'] ?? '');
$password = $_POST['password'] ?? '';
if ($login === '' || $password === '') {
$error = 'Заполните все поля.';
} else {
// Защита от перебора: задержка после неудачных попыток
$stmt = $pdo->prepare('SELECT attempts, last_attempt FROM login_attempts WHERE ip = ?');
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
$attempt = $stmt->fetch();
if ($attempt && $attempt['attempts'] >= 5 && time() - strtotime($attempt['last_attempt']) < 300) {
$error = 'Слишком много попыток. Необходимо подождать 5 минут.';
} else {
// Поиск пользователя
$stmt = $pdo->prepare('SELECT id, username, password_hash FROM users WHERE username = ?');
$stmt->execute([$login]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// Успешный вход: сброс попыток
$stmt = $pdo->prepare('DELETE FROM login_attempts WHERE ip = ?');
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
session_regenerate_id(true);
$_SESSION['user_id'] = $user['id'];
$_SESSION['username'] = $user['username'];
header('Location: dashboard.php');
exit;
} else {
// Неудачная попытка: увеличение счетчика
if ($attempt) {
$stmt = $pdo->prepare('UPDATE login_attempts SET attempts = attempts + 1, last_attempt = NOW() WHERE ip = ?');
} else {
$stmt = $pdo->prepare('INSERT INTO login_attempts (ip, attempts, last_attempt) VALUES (?, 1, NOW())');
}
$stmt->execute([$_SERVER['REMOTE_ADDR']]);
$error = 'Неверный логин или пароль.';
}
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Вход</title>
</head>
<body>
<?php if ($error): ?>
<p class="error"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<form method="post">
<label>Логин:<br><input type="text" name="login" required></label><br>
<label>Пароль:<br><input type="password" name="password" required></label><br>
<button type="submit">Войти</button>
</form>
</body>
</html>
Результат: страница с формой входа. При неверных данных выводится сообщение об ошибке. После 5 неудачных попыток с одного IP в течение 5 минут доступ блокируется.
Пример 2: Использование CSRF-токена и remember me
<?php
session_start();
require 'db.php';
require 'csrf.php'; // функции generateCsrfToken() и validateCsrfToken()
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!validateCsrfToken($_POST['csrf_token'] ?? '')) {
die('Ошибка CSRF.');
}
// ... валидация логина и пароля ...
if (isset($_POST['remember'])) {
$token = bin2hex(random_bytes(32));
$hashed = password_hash($token, PASSWORD_DEFAULT);
$expires = date('Y-m-d H:i:s', time() + 30*24*3600);
$stmt = $pdo->prepare('INSERT INTO user_tokens (user_id, token_hash, expires_at) VALUES (?, ?, ?)');
$stmt->execute([$user['id'], $hashed, $expires]);
setcookie('remember', $token, time() + 30*24*3600, '/', '', true, true);
}
// авторизация
}
?>
<form method="post">
<input type="hidden" name="csrf_token" value="<?= generateCsrfToken() ?>">
<label>Логин:<br><input type="text" name="login"></label><br>
<label>Пароль:<br><input type="password" name="password"></label><br>
<label><input type="checkbox" name="remember"> Запомнить меня</label><br>
<button type="submit">Войти</button>
</form>
Результат: форма с CSRF-защитой и опцией запомнить пользователя. При успешном входе создается токен в БД и куке.
Пример 3: Проверка куки remember при отсутствии сессии
<?php
session_start();
require 'db.php';
if (!isset($_SESSION['user_id']) && isset($_COOKIE['remember'])) {
$token = $_COOKIE['remember'];
$stmt = $pdo->prepare('SELECT user_id, token_hash, expires_at FROM user_tokens WHERE expires_at > NOW()');
$stmt->execute();
$tokens = $stmt->fetchAll();
foreach ($tokens as $row) {
if (password_verify($token, $row['token_hash'])) {
// Восстановление сессии
session_regenerate_id(true);
$_SESSION['user_id'] = $row['user_id'];
// Дополнительно можно подгрузить имя пользователя
break;
}
}
}
?>
Результат: если у пользователя есть валидная кука remember, сессия автоматически восстанавливается без повторного ввода пароля.