Реализация администраторской авторизации в PHP с примерами

Раздел: Безопасность и аутентификация -> Аутентификация и авторизация

Основные подходы к авторизации администратора в PHP

Сессионная авторизация с проверкой роли (наиболее эффективное решение)

Этот подход подразумевает хранение идентификатора пользователя и его роли (например, 'admin') в сессии PHP после успешной аутентификации. Авторизация (проверка прав) выполняется на каждой странице, требующей административного доступа, путём чтения данных из сессии.

Основные шаги реализации:

  1. Создание формы входа (логин, пароль).
  2. Валидация данных на стороне сервера.
  3. Проверка учётных данных (против хэша пароля, сохранённого в базе данных).
  4. Запуск сессии, сохранение идентификатора администратора и роли.
  5. Регенерация ID сессии после успешного входа для предотвращения фиксации сессии.
  6. Разграничение доступа к страницам на основе роли.

<?php
session_start();
require 'db.php';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    $password = $_POST['password'] ?? '';

    if (!$email || !$password) {
        $error = 'Пожалуйста, заполните все поля.';
    } else {
        $stmt = $pdo->prepare('SELECT id, password, role FROM users WHERE email = ?');
        $stmt->execute([$email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user['password']) && $user['role'] === 'admin') {
            session_regenerate_id(true);
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['role'] = $user['role'];
            header('Location: /admin/dashboard.php');
            exit;
        } else {
            $error = 'Неверный логин, пароль или недостаточно прав.';
        }
    }
}
?>

После входа в каждой странице админки добавляется проверка:


<?php
session_start();
if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') {
    header('HTTP/1.0 403 Forbidden');
    die('Доступ запрещён.');
}
?>

Типичные проблемы и их решения:

  • Фиксация сессии: решается регенерацией ID сессии при входе (session_regenerate_id).
  • XSS-атаки: экранирование вывода (htmlspecialchars) и установка флага HttpOnly для cookie сессии.
  • CSRF: использование токенов для критических операций (например, смена пароля).
  • SQL-инъекции: использование подготовленных запросов PDO или mysqli.
  • Слабый пароль: хэширование bcrypt (password_hash / password_verify).

Как организовать авторизацию администратора без сохранения состояния на сервере?

Использование JWT (JSON Web Token). Сервер выдаёт токен после аутентификации, клиент отправляет его с каждым запросом. Сервер проверяет подпись токена и извлекает роль администратора из полезной нагрузки.


<?php
// Генерация JWT (используется библиотека firebase/php-jwt)
require 'vendor/autoload.php';
use Firebase\JWT\JWT;

$key = 'секретный_ключ';
$payload = [
    'user_id' => $user['id'],
    'role' => $user['role'],
    'iat' => time(),
    'exp' => time() + 3600
];
$jwt = JWT::encode($payload, $key, 'HS256');
setcookie('admin_token', $jwt, time()+3600, '/', '', true, true); // Secure + HttpOnly
?>

<?php
// Проверка JWT на защищённой странице
$jwt = $_COOKIE['admin_token'] ?? '';
 try {
    $decoded = JWT::decode($jwt, new \Firebase\JWT\Key($key, 'HS256'));
    if ($decoded->role !== 'admin') throw new Exception('Не админ');
    // Доступ разрешён
} catch (Exception $e) {
    header('HTTP/1.0 401 Unauthorized');
    echo 'Токен недействителен.';
    exit;
}
?>

Возможные проблемы:

  • Токен может быть украден при XSS, если хранится в localStorage. Куки с флагами Secure и HttpOnly снижают риск.
  • Отзыв токена до истечения срока требует чёрного списка (сложнее).
  • Синхронизация ключа подписи при масштабировании.

Как использовать встроенную базовую аутентификацию для администратора?

Механизм HTTP Basic Auth прост, но мало подходит для веб-приложений без HTTPS из-за передачи пароля в открытом виде.


<?php
if (!isset($_SERVER['PHP_AUTH_USER'])) {
    header('WWW-Authenticate: Basic realm="Admin Zone"');
    header('HTTP/1.0 401 Unauthorized');
    echo 'Отменено пользователем';
    exit;
} else {
    $user = $_SERVER['PHP_AUTH_USER'];
    $pass = $_SERVER['PHP_AUTH_PW'];
    // Проверка против БД (аналогично сессии)
    if (checkAdmin($user, $pass)) {
        // Доступ разрешён
    } else {
        header('HTTP/1.0 401 Unauthorized');
        echo 'Неверные учётные данные';
        exit;
    }
}
?>

Проблемы:

  • Нет встроенного механизма выхода (браузер хранит учётные данные до закрытия).
  • Пароль передаётся в base64 без шифрования (требуется HTTPS).
  • Неудобно для реализации разграничения ролей (всегда только одно имя пользователя).

Как внедрить многофакторную авторизацию для администрирования?

После проверки пароля запрашивается одноразовый код (TOTP) через Google Authenticator. Реализация с помощью библиотеки PHPGangsta/GoogleAuthenticator.


<?php
// После успешной проверки пароля
$_SESSION['partial_auth'] = true; // флаг первого этапа
header('Location: /admin/verify-2fa.php');
?>

// verify-2fa.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $code = $_POST['code'];
    $ga = new PHPGangsta_GoogleAuthenticator();
    $secret = 'секрет администратора'; // из БД
    if ($ga->verifyCode($secret, $code, 2)) {
        $_SESSION['role'] = 'admin';
        unset($_SESSION['partial_auth']);
        header('Location: /admin/dashboard.php');
    } else {
        $error = 'Неверный код';
    }
}
?>

Проблемы:

  • Необходимость хранить секретные ключи для каждого администратора.
  • Пользователь может потерять устройство с TOTP.
  • Дополнительная нагрузка на UX.

Расширенные примеры кода для авторизации администратора

Ниже приведены подробные примеры, которые демонстрируют не только базовую авторизацию, но и дополнительные меры безопасности.

Пример 1: Полный скрипт входа с защитой от перебора

Пример

<?php
session_start();
require 'config.php';

$max_attempts = 5;
$lockout_time = 900; // 15 минут

$ip = $_SERVER['REMOTE_ADDR'];
$attempt_key = 'login_attempts_'.$ip;
$lock_key = 'login_lock_'.$ip;

// Проверка блокировки
if (isset($_SESSION[$lock_key]) && (time() - $_SESSION[$lock_key]) < $lockout_time) {
    die('Слишком много попыток. Попробуйте через 15 минут.');
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
    $password = $_POST['password'] ?? '';

    if (!$email || !$password) {
        $error = 'Заполните поля.';
    } else {
        // Проверка учётных данных
        $stmt = $pdo->prepare('SELECT id, password, role, name FROM admin_users WHERE email = ?');
        $stmt->execute([$email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user['password'])) {
            if ($user['role'] === 'admin') {
                session_regenerate_id(true);
                $_SESSION['user_id'] = $user['id'];
                $_SESSION['role'] = $user['role'];
                $_SESSION['name'] = $user['name'];
                // Сброс счётчика попыток
                unset($_SESSION[$attempt_key], $_SESSION[$lock_key]);
                header('Location: dashboard.php');
                exit;
            } else {
                $error = 'Недостаточно прав (роль не admin).';
            }
        } else {
            // Увеличиваем счётчик неудачных попыток
            $_SESSION[$attempt_key] = ($_SESSION[$attempt_key] ?? 0) + 1;
            if ($_SESSION[$attempt_key] >= $max_attempts) {
                $_SESSION[$lock_key] = time();
                $error = 'Аккаунт заблокирован из-за множества неудачных попыток.';
            } else {
                $error = 'Неверный email или пароль.';
            }
        }
    }
}
?>
(При первой неудачной попытке пользователь видит «Неверный email или пароль.»; после 5 ошибок появляется блокировка на 15 минут.)

Пример 2: Middleware для проверки прав администратора (диспетчерский паттерн)

Пример

<?php
// admin_check.php
session_start();

function isAdmin(): bool {
    return isset($_SESSION['role']) && $_SESSION['role'] === 'admin';
}

function requireAdmin(): void {
    if (!isAdmin()) {
        header('HTTP/1.0 403 Forbidden');
        include '403.php';
        exit;
    }
}

// Использование на защищённой странице
require_once 'admin_check.php';
requireAdmin();

// Остальной код страницы
?>
(Страница выводит кастомную страницу 403, если пользователь не является администратором.)

Пример 3: Авторизация через JWT с обновлением токена (refresh token)

Пример

<?php
// login.php - выдача access и refresh токенов
use Firebase\JWT\JWT;

$access_key = 'access_secret';
$refresh_key = 'refresh_secret';

// После успешной аутентификации
$access_payload = [
    'sub' => $user['id'],
    'role' => 'admin',
    'iat' => time(),
    'exp' => time() + 900 // 15 минут
];
$refresh_payload = [
    'sub' => $user['id'],
    'token_hash' => bin2hex(random_bytes(16)),
    'iat' => time(),
    'exp' => time() + 86400 * 7 // 7 дней
];
$access_token = JWT::encode($access_payload, $access_key, 'HS256');
$refresh_token = JWT::encode($refresh_payload, $refresh_key, 'HS256');
// Сохраняем refresh в БД вместе с токеном хэша для отзыва
setcookie('access_token', $access_token, time()+900, '/', '', true, true);
setcookie('refresh_token', $refresh_token, time()+86400*7, '/', '', true, true);
?>

// refresh.php - обновление access токена
$refresh = $_COOKIE['refresh_token'] ?? '';
try {
    $decoded = JWT::decode($refresh, new \Firebase\JWT\Key($refresh_key, 'HS256'));
    // Проверка существования refresh токена в БД
    $stmt = $pdo->prepare('SELECT id, role FROM admin_users WHERE id = ?');
    $stmt->execute([$decoded->sub]);
    $user = $stmt->fetch();
    if (!$user) throw new Exception('Пользователь не найден');
    
    $new_access = JWT::encode([
        'sub' => $user['id'],
        'role' => $user['role'],
        'iat' => time(),
        'exp' => time() + 900
    ], $access_key, 'HS256');
    setcookie('access_token', $new_access, time()+900, '/', '', true, true);
} catch (Exception $e) {
    header('HTTP/1.0 401 Unauthorized');
    echo 'Требуется повторный вход.';
}
?>
(Клиент автоматически обновляет access токен, когда тот истекает, используя refresh cookie. Это снижает риск компрометации долгоживущего токена.)

Пример 4: Иерархия ролей (superadmin, admin, moderator)

Пример

<?php
// RBAC простейшая реализация с конфигурацией
$roles = [
    'superadmin' => ['permissions' => ['manage_admins', 'delete_content', 'edit_settings']],
    'admin' => ['permissions' => ['delete_content', 'edit_settings']],
    'moderator' => ['permissions' => ['edit_content']],
];

function hasPermission(string $permission, string $role): bool {
    global $roles;
    return in_array($permission, $roles[$role]['permissions'] ?? []);
}

// Проверка на странице удаления контента
if (!hasPermission('delete_content', $_SESSION['role'])) {
    die('Недостаточно прав для удаления.');
}
?>
(Администраторы с ролью admin могут удалять контент, а модераторы - только редактировать. Superadmin может управлять другими администраторами.)

Авторизация администратора в PHP - comments

En
Php admin authorize (php)