Аутентификация и сеансы¶
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¶
Способ входа в систему администратора. Шаги по порядку:
- Проверка повторной проверки (если параметр
recaptcha_enableвключен и не пропущен). - Поиск учетных данных с помощью
UserRepository::getAuthUserByCredentials(). - Проверка группы кодов доступа - имя пользователя
member_group_idдолжно входить в разрешенные группы текущего кода доступа, в противном случае кодов доступа не должно существовать. - Permission check --
is_adminmust be true for the user's group. - Проверка состояния -
$rUserInfo['status'] == 1(включено). - В случае успеха: повторно хэширует пароль, обновляет
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 | У пользователя нет разрешения торгового посредника |
Хэширование паролей¶
Использует crypt() с SHA-512 ($6$). Формат salt равен $6$rounds=20000$<salt>$, где <salt> - это 16 шестнадцатеричных символов, полученных из openssl_random_pseudo_bytes(16). Пароли повторно хэшируются при каждом успешном входе в систему, что приводит к замене значения salt.
Проверяет пароль в виде открытого текста на соответствие сохраненному хэшу, используя crypt($password, $storedHash), с возможностью сравнения по времени с помощью hash_equals(). Сохраненный хэш содержит алгоритм, раунды и соль, поэтому crypt() воспроизводит правильный хэш для сравнения.
Аутентификация игрока¶
Файл: src/Public/Controllers/Player/PlayerLoginController.php
Процедура входа игрока в систему принципиально отличается от процедуры администратора/реселлера. Она аутентифицирует "линии" конечного пользователя (подписки на IPTV), а не операторов панели.
Процесс входа в систему¶
PlayerLoginController::processLogin() выполняет эти проверки в порядке:
- Поиск учетных данных --
UserRepository::getUserInfo()(отличается отgetAuthUserByCredentials, используемого администратором/реселлером). - Отклонение типа линии -- Линии E2, MAG и Stalker отклоняются с определенными кодами ошибок.
- Проверка истечения срока годности --
exp_dateдолжно быть равно null или в будущем. - Проверка с поддержкой администратора --
admin_enabled == 0возвращаетCLIENT_BANNED. - Проверка, включенная пользователем --
enabled == 0возвращаетCLIENT_DISABLED. - Список разрешенных IP-адресов -- Если для пользователя задано значение
allowed_ips, IP-адрес клиента должен совпадать (решается с помощьюgethostbyname). - Ограничение по стране - Два режима:
- Для каждого пользователя: если задано значение
forced_country, а неALL, страна GeoIP должна совпадать. - Глобальный: если нет переопределения для каждого пользователя, устанавливается глобальный параметр
allow_countries(если только он не содержитALL). - Проверка агента пользователя -- Если для пользователя задано значение
allowed_ua, то пользовательский агент HTTP должен соответствовать. - флаг Проверка интернет-провайдера --
isp_violateотклоняет соединение. - Проверка сервера интернет-провайдера -- Если значение
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.
Проверка сеанса администратора¶
Точка входа: AdminScopeBootstrap::hydrateAdminContext() (src/Infrastructure/Bootstrap/AdminScopeBootstrap.php). Он запускается по пути администратора фронт-контроллера (через boot()) и вызывается непосредственно сценариями просмотра $noBootstrapPages (login, setup, database, logout, player, post) — заменяя прежний admin/functions.php include.
Когда установлено значение $_SESSION['hash'], он определяет пользователя (UserRepository::getRegisteredUserById($_SESSION['hash'])) и права доступа (AuthRepository::getPermissions()), а затем проверяет целостность сеанса с помощью SessionManager::adminSessionValid($rUserInfo, $rPermissions, $rSettings):
- Пользователь и администратор - существует строка пользователя и разрешения, и задано значение
is_admin. - Проверка IP-адреса - Сравнивает текущий IP-адрес с
$_SESSION['ip']: - Если параметр
ip_subnet_matchвключен: сравниваются только первые три октета (например,192.168.1.*соответствует192.168.1.*). - Если параметр
ip_subnet_matchотключен: требуется точное совпадение IP-адресов. - IP-адрес применяется только тогда, когда включено значение
ip_logout; в противном случае допускается изменение IP-адреса (и в пути HTML значение$_SESSION['ip']обновляется до нового значения). - Проверить проверку хэша Verify --
$_SESSION['verify']должно быть равноmd5($rUserInfo['username'] . '||' . $rUserInfo['password']). Это гарантирует, что сеанс будет аннулирован в случае изменения пароля.
Если проверка завершается неудачно, сеанс очищается с помощью SessionManager::clearContext('admin'), и пользователь перенаправляется на индексную страницу. Конечная точка admin JSON DataTables (Public\Controllers\Admin\TableController) выполняет ту же проверку SessionManager::adminSessionValid() в своей ветви сеанса, но вместо перенаправления выдает JSON.
Проверка сеанса работы с реселлером¶
Файл: src/Infrastructure/Bootstrap/reseller_functions.php
Логика идентична проверке администратора, но используются сеансовые ключи реселлера:
- Проверяет
$_SESSION['reseller']для идентификатора пользователя. - Использует
$_SESSION['rip']для сравнения IP-адресов. - Использует
$_SESSION['rverify']для проверки хэша. - Проверяет разрешение
is_resellerвместоis_admin.
Соответствие IP-подсети и поведение при выходе из системы по IP-адресу такое же, как у администратора.
Жизненный цикл сеанса администратора¶
Запуск сеанса и 60-минутный тайм-аут бездействия выполняются в AdminScopeBootstrap::bootSession() (путь к интерфейсу контроллера), а для устаревших сценариев просмотра — в SessionManager::start('admin') + requireAuth(), которые заменили прежний параметр admin/session.php. Если заданы значения $_SESSION['hash'] и $_SESSION['last_activity'], а с момента last_activity прошло более 60 минут, ключи сеанса (hash, ip, code, verify, last_activity) не заданы. При каждом действительном запросе обновляется $_SESSION['last_activity'], и сеанс закрывается для записи. Конечная точка опроса AJAX-сессии ({"result": true/false}) обслуживается Public\Controllers\Admin\SessionController (маршрут session).
Проверка сеанса игрока¶
Контекст проигрывателя не выполняет проверку IP-адреса, соответствие подсети или тайм-аут активности на уровне сеанса. Сохраняются только phash и pverify, и проверка выполняется на прикладном уровне для повторной проверки этих значений по базе данных.
Менеджер сеанса¶
Файл: src/Core/Auth/SessionManager.php
Унифицированный сеансовый API, который абстрагирует различные имена сеансовых ключей в разных контекстах. Он заменяет устаревший admin session.php / functions.php includes (теперь удален); области реселлеров и игроков загружаются с помощью своих собственных классов *ScopeBootstrap.
Контекстная ключевая карта¶
| Логический ключ | Ключ администратора $_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
Перенаправляет пользователей, не прошедших проверку подлинности, на страницу входа в систему; в случае успеха вызывает touch() для обновления временной метки активности. Опрос выделенного сеанса AJAX теперь выполняется с помощью SessionController (маршрут session), а не с помощью этого метода.
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¶
В контексте начальной загрузки администратора 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']) вычисляется следующим образом:
Это значение сравнивается с текущими значениями базы данных при каждой загрузке страницы. Если администратор изменяет пароль пользователя (что изменяет сохраненный хэш), все существующие сеансы для этого пользователя автоматически становятся недействительными, поскольку проверяемый хэш больше не будет совпадать.
Для логинов администратора и торгового посредника пароль повторно хэшируется во время входа в систему, поэтому используется $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/Infrastructure/Bootstrap/AdminScopeBootstrap.php |
Загрузка администратора: жизненный цикл сеанса, $rUserInfo/$rPermissions, защита целостности |
src/Public/Controllers/Admin/SessionController.php |
Опрос сессии администратора AJAX (маршрут session) |
src/Infrastructure/Bootstrap/reseller_functions.php |
Проверка сеанса реселлера при каждой загрузке страницы |
src/Domain/User/UserRepository.php |
Поиск учетных данных (getAuthUserByCredentials) |
src/bootstrap.php |
Определения констант состояния, контексты начальной загрузки |
src/Core/Config/ConstantsInitializer.php |
FLOOD_TMP_PATH определение (paths() карта) |