Разработка защищенного входа: от простого к сложному

Раздел: Веб-разработка -> Аутентификация

Основные подходы к реализации входа на 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);
// При последующем визите без сессии проверяем куку
Токены должны быть случайными и храниться в хешированном виде. Уязвимость: если злоумышленник украдет куку, он получит доступ. Рекомендуется ограничивать количество токенов на пользователя.
- Service login php (сервис аутентификации php)
- Login php file (файл входа php)
- Index php id login (авторизация через параметр id в index.php)

Расширенные примеры реализации аутентификации

Ниже представлены более детальные сценарии с пояснениями каждого шага.

Пример 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, сессия автоматически восстанавливается без повторного ввода пароля.

Логин (авторизация) в PHP - comments

En
Index php r login (php)