Files
XC_VM/docs/ru/guides/authentication-and-sessions.md
T
2026-09-16 22:04:48 +03:00

36 KiB
Raw Blame History

Аутентификация и сеансы

XC_VM поддерживает три контекста аутентификации - администратора, реселлера и игрока - каждый с изолированными сеансовыми ключами, отдельными потоками входа в систему и независимой логикой проверки. В этом документе описан полный жизненный цикл аутентификации от входа в систему до проверки сеанса и обеспечения безопасности.


Обзор процесса входа в систему

Все три контекста следуют схожему шаблону высокого уровня, с зависящими от контекста различиями в проверке и хранении сеанса.

Взаимодействие администратора и реселлера

POST request with credentials
  -> BruteforceGuard checks (flood / brute-force)
  -> Optional reCAPTCHA verification
  -> Credential lookup via UserRepository::getAuthUserByCredentials()
  -> Access code / group validation
  -> Permission check (is_admin or is_reseller)
  -> User status check (enabled/disabled)
  -> Password re-hash + session write + login log

Поток игроков

POST request with credentials
  -> UserRepository::getUserInfo() lookup
  -> Line type checks (reject E2, MAG, Stalker)
  -> Expiration date check
  -> admin_enabled / enabled status checks
  -> IP allowlist, country restriction, user agent, ISP checks
  -> Session write + redirect
  -> BruteforceGuard::checkFlood() on any failure

Аутентификатор

Файл: src/Core/Auth/Authenticator.php

Authenticator::login(array $data, bool $bypassRecaptcha = false): array

Способ входа в систему администратора. Шаги по порядку:

  1. Проверка повторной проверки (если параметр recaptcha_enable включен и не пропущен).
  2. Поиск учетных данных с помощью UserRepository::getAuthUserByCredentials().
  3. Проверка группы кодов доступа - имя пользователя member_group_id должно входить в разрешенные группы текущего кода доступа, в противном случае кодов доступа не должно существовать.
  4. Permission check -- is_admin must be true for the user's group.
  5. Проверка состояния - $rUserInfo['status'] == 1 (включено).
  6. В случае успеха: повторно хэширует пароль, обновляет last_login и ip в базе данных, переносит сеанс на новый идентификатор (session_regenerate_id(true)), записывает ключи сеанса, регистрирует логин.

Новый идентификатор имеет значение: идентификатор, с которым посетитель вводит форму входа в систему, может быть известен кому-то еще (файл cookie, установленный с дочернего поддомена, общего компьютера), и, сохранив его, пользователь также войдет в систему. resellerLogin() и страница настройки при первом запуске делает то же самое.

Значения сеанса, записанные в зависимости от успеха:

$_SESSION['hash']   = $rUserInfo['id'];       // User ID
$_SESSION['ip']     = $rIP;                   // Client IP at login
$_SESSION['code']   = AuthRepository::getCurrentCode(); // Current access code
$_SESSION['verify'] = md5($rUserInfo['username'] . '||' . $rCrypt); // Verification hash

Authenticator::resellerLogin(array $data): array

Способ входа в систему реселлера. Структура, идентичная login(), с этими различиями:

  • Повторная проверка всегда проверяется, когда она включена (параметр обхода отсутствует).
  • Для проверки разрешений требуется is_reseller вместо is_admin.
  • Возвращает STATUS_NOT_RESELLER, если у пользователя нет разрешения торгового посредника.
  • Логи входа в систему записываются с типом RESELLER вместо ADMIN.

Значения сеанса, записанные в зависимости от успеха:

$_SESSION['reseller'] = $rUserInfo['id'];       // User ID
$_SESSION['rip']      = $rIP;                   // Client IP at login
$_SESSION['rcode']    = AuthRepository::getCurrentCode(); // Current access code
$_SESSION['rverify']  = md5($rUserInfo['username'] . '||' . $rCrypt); // Verification hash

Константы статуса входа в систему

Определено в src/bootstrap.php через XC_Bootstrap::defineStatusConstants():

Постоянный Ценность Значение
STATUS_FAILURE 0 Общий сбой (неверные учетные данные или универсальная ошибка)
STATUS_SUCCESS 1 Вход в систему был успешным
STATUS_DISABLED 5 Учетная запись отключена
STATUS_NOT_ADMIN 6 У пользователя нет прав администратора
STATUS_INVALID_CAPTCHA 12 Не удалось выполнить проверку reCAPTCHA
STATUS_INVALID_CODE 13 Несоответствие кода доступа и группы
STATUS_NOT_RESELLER 35 У пользователя нет разрешения торгового посредника

Хэширование паролей

Authenticator::hashPassword(string $password, ?string $salt = null, int $rounds = 20000): string

Использует crypt() с SHA-512 ($6$). Используется формат $6$rounds=20000$<salt>$, где <salt> - это 16 шестнадцатеричных символов, полученных из openssl_random_pseudo_bytes(16). Пароли повторно хэшируются при каждом успешном входе в систему, что приводит к перераспределению ресурсов.

Authenticator::checkPassword(string $password, string $storedHash): bool

Проверяет открытый текстовый пароль на соответствие сохраненному хэшу, используя crypt($password, $storedHash), с безопасным по времени сравнением с помощью hash_equals(). Сохраненный хэш содержит алгоритм, раунды и соль, поэтому crypt() воспроизводит правильный хэш для сравнения.


Аутентификация игрока

Файл: src/Public/Controllers/Player/PlayerLoginController.php

Процедура входа игрока в систему принципиально отличается от процедуры администратора/реселлера. Она позволяет аутентифицировать "линии" конечного пользователя (подписки на IPTV), а не операторов панели.

Процесс входа в систему

PlayerLoginController::processLogin() выполняет эти проверки в порядке:

  1. Поиск учетных данных -- UserRepository::getUserInfo() (отличается от getAuthUserByCredentials, используемого администратором/реселлером).
  2. Отклонение типа линии -- Линии E2, MAG и Stalker отклоняются с определенными кодами ошибок.
  3. Проверка истечения срока годности -- exp_date должно быть равно null или в будущем.
  4. Проверка с поддержкой администратора -- admin_enabled == 0 возвращает CLIENT_BANNED.
  5. Проверка, включенная пользователем -- enabled == 0 возвращает CLIENT_DISABLED.
  6. Список разрешенных IP-адресов -- Если для пользователя задано значение allowed_ips, IP-адрес клиента должен совпадать (решается с помощью gethostbyname).
  7. Ограничение по стране - Два режима:
    • Для каждого пользователя: если задано значение forced_country, а не ALL, страна GeoIP должна совпадать.
    • Глобальный: если нет переопределения для каждого пользователя, устанавливается глобальный параметр allow_countries (если только он не содержит ALL).
  8. Проверка агента пользователя -- Если для пользователя задано значение allowed_ua, то пользовательский агент HTTP должен соответствовать.
  9. флаг Проверка интернет-провайдера -- isp_violate отклоняет соединение.
  10. Проверка сервера интернет-провайдера -- Если значение isp_is_server равно true и пользователь не является рестримером, соединение будет отклонено.

Каждый сбой запускает BruteforceGuard::checkFlood() перед возвратом кода ошибки.

Коды ошибок проигрывателя

Постоянный Ценность Значение
CLIENT_INVALID 0 Неверное имя пользователя или пароль
CLIENT_IS_E2 1 Линии Enigma запрещены
CLIENT_IS_MAG 2 МАГНИТНЫЕ линии не допускаются
CLIENT_IS_STALKER 3 Линии преследования запрещены
CLIENT_EXPIRED 4 Срок действия строки истек
CLIENT_BANNED 5 Строка заблокирована (admin_enabled = 0)
CLIENT_DISABLED 6 Линия отключена (включено = 0)
CLIENT_DISALLOWED 7 Не удалось выполнить проверку IP/страны/UA/провайдера

Ключи к сеансу игрока

$_SESSION['phash']   = $rUserInfo['id'];
$_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password']);

В контексте player хранятся только два сеансовых ключа. В отличие от ключей администратора и реселлера, здесь нет ключей activity, ip или code. Это означает, что в сеансе player нет таймаута бездействия и не обнаруживается изменение IP-адреса на уровне сеанса.


Проверка сеанса при загрузке страницы

После первоначального входа в систему при каждой загрузке страницы, прошедшей проверку подлинности, выполняется повторная проверка сеанса. Это происходит в файлах начальной загрузки, а не в SessionManager.

Проверка сеанса администратора

Файл: src/Public/Views/admin/functions.php

Если установлено значение $_SESSION['hash'], при каждой загрузке страницы выполняются следующие проверки:

  1. Поиск пользователя -- UserRepository::getRegisteredUserById($_SESSION['hash']). Если пользователь больше не существует, сеанс завершается.
  2. Проверка прав доступа -- AuthRepository::getPermissions() должен возвращать допустимый набор с is_admin == true.
  3. Проверка IP-адреса - Сравнивает текущий IP-адрес с $_SESSION['ip']:
    • Если параметр ip_subnet_match включен: сравниваются только первые три октета (например, 192.168.1.* соответствует 192.168.1.*).
    • Если параметр ip_subnet_match отключен: требуется точное совпадение IP-адресов.
    • Если IP-адрес не совпадает и включена настройка ip_logout, сеанс завершается.
    • Если IP-адрес не совпадает и ip_logout отключен, $_SESSION['ip'] автоматически обновляется до нового IP-адреса.
  4. Проверить проверку хэша Verify -- $_SESSION['verify'] должно быть равно md5($rUserInfo['username'] . '||' . $rUserInfo['password']). Это гарантирует, что сеанс будет аннулирован в случае изменения пароля.

Если какая-либо проверка завершается неудачей, сеанс очищается с помощью SessionManager::clearContext('admin'), и пользователь перенаправляется на индексную страницу.

Проверка сеанса работы с реселлером

Файл: src/Infrastructure/Bootstrap/reseller_functions.php

Логика идентична проверке администратора, но используются сеансовые ключи реселлера:

  • Проверяет $_SESSION['reseller'] для идентификатора пользователя.
  • Использует $_SESSION['rip'] для сравнения IP-адресов.
  • Использует $_SESSION['rverify'] для проверки хэша.
  • Проверяет разрешение is_reseller вместо is_admin.

Соответствие IP-подсети и поведение при выходе из системы по IP-адресу такое же, как у администратора.

Тайм-аут сеанса администрирования

Файл: src/Public/Views/admin/session.php

Для сеансов администрирования выполняется отдельная проверка времени ожидания сеанса. Если заданы значения $_SESSION['hash'] и $_SESSION['last_activity'], а с момента last_activity прошло более 60 минут, то сеансовые ключи (hash, ip, code, verify, last_activity) не заданы. При каждом действительном запросе обновляется $_SESSION['last_activity'], и сессия закрывается для записи.

Проверка сеанса игрока

Контекст проигрывателя не выполняет проверку IP-адреса, соответствие подсети или тайм-аут активности на уровне сеанса. Сохраняются только phash и pverify, и проверка выполняется на прикладном уровне для повторной проверки этих значений по базе данных.


Менеджер сеанса

Файл: src/Core/Auth/SessionManager.php

Унифицированный сеансовый API, который абстрагирует различные имена сеансовых ключей в разных контекстах. Предназначен для замены устаревших файлов admin/session.php и reseller/session.php.

Контекстная ключевая карта

Логический ключ Ключ администратора $_SESSION Ключ реселлера $_SESSION Ключ игрока $_SESSION
auth hash reseller phash
activity last_activity rlast_activity --
ip ip rip --
code code rcode --
verify verify rverify pverify

Контекст игрока намеренно опускает сопоставления activity, ip и code.

Методы

start(string $context, int $timeout = 60): void

Запускает сеанс PHP (если он еще не запущен), устанавливает активный контекст и запускает checkTimeout() для завершения устаревших сеансов. Контекст должен быть 'admin', 'reseller' или 'player'.

requireAuth(?string $loginUrl = null): void

Проверяет наличие аутентифицированного сеанса. Если запрос направлен напрямую на session.php, возвращает ответ в формате JSON {"result": true/false} (используется для опроса сеанса AJAX). В противном случае перенаправляет не прошедших проверку пользователей на страницу входа в систему. В случае успеха вызывает touch() для обновления временной метки действия.

isAuthenticated(): bool

Проверка на отсутствие блокировки. Возвращает значение true, если сеанс был запущен и установлен ключ auth.

getUser(): mixed

Возвращает значение, сохраненное в ключе сеанса auth (идентификатор пользователя для администратора/реселлера или идентификатор строки для игрока), или null, если аутентификация не пройдена.

getValue(string $name): mixed

Возвращает значение сеанса по его логическому имени (auth, activity, ip, code, verify). Логическое имя сопоставляется с фактическим ключом $_SESSION на основе текущего контекста.

setValue(string $name, mixed $value): void

Устанавливает значение сеанса по логическому имени.

login(mixed $hash, ?string $ip = null): void

Создает аутентифицированный сеанс, задавая значения auth и activity. При необходимости сохраняет IP-адрес клиента.

destroy(): void

Очищает все ключи сеанса для текущего контекста. Если никакой другой контекст не активен (проверяет противоположный контекст администратора/реселлера), уничтожает весь сеанс PHP.

clearContext(string $context): void

Очищает все ключи сеанса для определенного контекста, не разрушая сеанс. Простая замена устаревшего destroySession($type).

touch(): void

Обновляет $_SESSION[$activityKey] до текущей временной метки и вызывает session_write_close(), чтобы снять блокировку сеанса.

getContext(): ?string

Возвращает текущую контекстную строку ('admin', 'reseller', 'player') или null, если она не задана.

Поведение по истечении времени ожидания

SessionManager::DEFAULT_TIMEOUT равно 60 минутам. Метод checkTimeout() (вызываемый автоматически start()) сравнивает время, прошедшее с момента last_activity. Если время ожидания превышено, все ключи сеанса, зависящие от контекста, сбрасываются, что приводит к выходу пользователя из системы.

Поскольку контекст игрока не имеет ключа activity в карте ключей, проверка тайм-аута не применяется к сессиям игрока.


Охранник с применением грубой силы

Файл: src/Core/Auth/BruteforceGuard.php

Централизованное ограничение скорости и защита от перебора. Все методы используют состояние на основе файла, хранящееся в FLOOD_TMP_PATH (/home/xc_vm/tmp/flood/). Разрешенные IP-адреса (IP-адреса сервера) и IP-адреса, указанные в параметре flood_ips_exclude, всегда исключаются.

checkFlood(?string $ip = null, bool $useCachedMode = false): void

Скорость - ограничивает количество запросов по IP-адресу в пределах настраиваемого временного интервала.

  • Настройки: flood_limit (максимальное количество запросов), flood_seconds (размер окна).
  • Файл состояния: FLOOD_TMP_PATH . $ip - сохраняет объект JSON с числом requests и временной меткой last_request.
  • Поведение: Отслеживает количество запросов в пределах временного окна. Если количество превышает flood_limit, IP-адрес блокируется (заносится в таблицу blocked_ips или передается через Redis в кэшированном/потоковом режиме). Файл состояния удаляется после блокировки.
  • Используется: Вход игрока (вызывается при каждой неудачной попытке входа в систему), конечные точки потоковой передачи.

checkBruteforce(?string $ip = null, ?string $mac = null, ?string $username = null, bool $useCachedMode = false): void

Обнаруживает атаки методом перебора на основе количества уникальных MAC-адресов или имен пользователей, обнаруженных с одного IP-адреса.

  • Настройки: bruteforce_mac_attempts, bruteforce_username_attempts ( максимальное количество уникальных значений), bruteforce_frequency (временной интервал в секундах).
  • Файл состояния: FLOOD_TMP_PATH . $ip . '_mac' или FLOOD_TMP_PATH . $ip . '_user' - сохраняет попытки в виде пар {term: timestamp}.
  • Поведение: Попытки с истекшим сроком действия (за пределами частотного диапазона) отсекаются с помощью truncateAttempts(). Если количество уникальных запросов превышает допустимое, IP-адрес блокируется.
  • Используется: Конечные точки потоковой аутентификации.

checkAuthFlood(array $user, ?string $ip = null): void

Ограничивает скорость запросов на аутентификацию для конкретной комбинации пользователь +IP. Предназначен для ограничения повторных попыток авторизации без полной блокировки.

  • Настройки: auth_flood_limit (максимальное количество попыток), auth_flood_seconds (окно), auth_flood_sleep (задержка в секундах при блокировке).
  • Файл состояния: FLOOD_TMP_PATH . $userId . '_' . $ip - сохраняет попытки в виде индексированных временных меток плюс необязательную временную метку block_until.
  • Поведение: Когда количество попыток превышает допустимое значение, устанавливается временная метка block_until. Последующие запросы в течение периода блокировки задерживаются на auth_flood_sleep секунды (через sleep()). IP-адрес не блокируется навсегда. Пользователи Restreamer (is_restreamer) освобождаются от этого требования.
  • Используется: Потоковая аутентификация.

truncateAttempts(array $attempts, int $frequency, bool $list = false): array

Отфильтровывает просроченные попытки из массива отслеживания. Если значение $list равно true, массив обрабатывается как индексированный (для checkAuthFlood); в противном случае как ассоциативный, с ключом по термину (для checkBruteforce).

Блокирующий механизм

Когда IP-адрес заблокирован:

  • Обычный режим: Выполняет вставку в таблицу базы данных blocked_ips с указанием причины (FLOOD ATTACK или BRUTEFORCE MAC/USER ATTACK) и обновляет кэш BlocklistService.
  • Режим кэширования/потоковой передачи ($useCachedMode = true): Устанавливает сигнал Redis (bruteforce_attack/$ip или flood_attack/$ip) через RedisManager::setSignal() для блокировки потокового контекста без записи в базу данных.
  • В обоих режимах выполняется касание файла-маркера FLOOD_TMP_PATH . 'block_' . $ip для быстрой проверки на уровне файловой системы.

Безопасность сеанса

В контексте начальной загрузки администратора сеансовый файл cookie имеет значения SameSite=Strict и HttpOnly, а PHP работает в строгом режиме, отказываясь от идентификаторов сеанса, которые он никогда не выдавал:

$params['samesite'] = 'Strict';
$params['httponly'] = true;
session_set_cookie_params($params);
ini_set('session.use_strict_mode', '1');
session_start();

Ни один скрипт панели не считывает сессионный файл cookie, поэтому HttpOnly ничего не стоит и не позволяет XSS прочитать его.

Проверка хэша

Проверяющий хэш ($_SESSION['verify'] / $_SESSION['rverify'] / $_SESSION['pverify']) вычисляется следующим образом:

md5($username . '||' . $hashedPassword)

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

Для логинов администратора и торгового посредника пароль повторно хэшируется во время входа в систему, поэтому используется $rCrypt (новый хэш). Для логинов игроков используется существующий сохраненный хэш $rUserInfo['password'].

Обработка изменений IP-адреса

Два параметра управляют поведением при изменении IP-адреса:

Установка Эффект
ip_logout Если включено, завершает сеанс при изменении IP-адреса клиента (точного или из подсети, в зависимости от ip_subnet_match).
ip_subnet_match Когда этот параметр включен, сравниваются только первые три октета IP-адреса, вместо того чтобы требовать точного совпадения. Позволяет пользователям с динамическими IP-адресами в пределах одной подсети сохранять сеанс связи.

Когда ip_logout отключено и IP-адрес изменяется, сохраненный IP-адрес сеанса автоматически обновляется до нового IP-адреса.


Регистрация входа в систему

Неудачные входы в систему (INVALID_LOGIN) всегда регистрируются, поскольку они учитываются в соответствии с лимитом потока входов. Если параметр save_login_logs включен, все остальные результаты также записываются. Все это заносится в таблицу login_logs:

INSERT INTO `login_logs`(`type`, `access_code`, `user_id`, `status`, `login_ip`, `date`)
VALUES($type, $codeId, $userId, $status, $ip, $timestamp);
Колонка Описание
type ADMIN или RESELLER
access_code Идентификатор текущего кода доступа
user_id Идентификатор пользователя (0 для неверных учетных данных)
status SUCCESS, INVALID_LOGIN, INVALID_CODE, NOT_ADMIN, DISABLED
login_ip IP-адрес клиента
date Временная метка Unix

Логины игроков не записываются в login_logs.

Ограничение потока данных для входа в систему

Страницы входа администратора и реселлера запрашивают Authenticator::loginFloodExceeded($ip, $rSettings['login_flood']) перед обработкой входа. Если адрес содержит login_flood или более INVALID_LOGIN строк, датированных в течение последних 24 часов, он добавляется в список заблокированных (LOGIN FLOOD ATTACK), и запрос завершается. Значение login_flood из 0 отменяет ограничение. Быстрые инструменты → Очистить поток данных для входа в систему удаляет подсчитанные строки.

date - это временная метка Unix, поэтому значение окна равно date >= time() - 86400. Страницы, используемые для фильтрации, имеют значение TIME_TO_SEC(TIMEDIFF(NOW(), date)), которое равно нулю для целочисленного столбца, поэтому ни один адрес не был заблокирован.


Авторизация (после входа в систему)

После аутентификации два дополнительных уровня авторизации определяют, к чему пользователь может получить доступ:

Authorization

Файл: src/Core/Auth/Authorization.php

Авторизация на уровне объекта. Проверяет, есть ли у текущего пользователя разрешение на доступ к определенному ресурсу (пользователь, поток и т.д.) на основе иерархии владельцев реселлеров и групповых разрешений.

  • Authorization::hasResellerPermissions($type) -- устанавливает флажок единственного разрешения на $rPermissions.
  • Authorization::check($type, $id) -- проверяет доступ к определенному ресурсу по типу и идентификатору.

PageAuthorization

Файл: src/Core/Auth/PageAuthorization.php

Управление доступом на уровне страницы. Определяет, разрешают ли групповые разрешения текущего пользователя доступ к определенной странице панели администратора или торгового посредника.

  • PageAuthorization::checkResellerPermissions($page) - сопоставляет названия страниц с флагами требуемых разрешений и возвращает, разрешен ли доступ.

Связанные файлы

Файл Цель
src/Core/Auth/Authenticator.php Логика входа администратора и реселлера в систему, хэширование паролей
src/Core/Auth/SessionManager.php Унифицированный сеансовый API с сопоставлением контекстных ключей
src/Core/Auth/BruteforceGuard.php Ограничение скорости и защита от перебора
src/Core/Auth/Authorization.php Проверки авторизации на уровне объекта
src/Core/Auth/PageAuthorization.php Управление доступом на уровне страницы
src/Public/Controllers/Player/PlayerLoginController.php Процесс входа игрока в систему с проверкой безопасности
src/Public/Views/admin/functions.php Проверка сеанса администратора при каждой загрузке страницы
src/Public/Views/admin/session.php Тайм-аут сеанса администратора и проверка сеанса AJAX
src/Infrastructure/Bootstrap/reseller_functions.php Проверка сеанса реселлера при каждой загрузке страницы
src/Domain/User/UserRepository.php Поиск учетных данных (getAuthUserByCredentials)
src/bootstrap.php Определения констант состояния, контексты начальной загрузки
src/Core/Config/ConstantsInitializer.php FLOOD_TMP_PATH определение (paths() карта)