Представим сайт частной практики: юрист Анна ведёт консультацию с клиентом Игорем в 10:00, а в то же время психолог Борис принимает Марину. Условные имена здесь выдуманы, но задача настоящая: две видеовстречи идут параллельно, и ни Игорь, ни Марина не должны увидеть чужой экран, услышать чужой голос или хотя бы заметить, что рядом существует другая комната. Звучит как мелочь, пока не выясняется, что «комната» в видеосвязи сама по себе ничего не защищает. Защищает тот, кто решает, кому выдать пропуск.
В схеме с LiveKit этот пропуск называется token. Всё остальное в статье вращается вокруг одного вопроса: как связать авторизацию вашего сайта, где человек вошёл в личный кабинет и записался на приём, с комнатами медиасервера, который о вашем кабинете ничего не знает.
Запись на консультацию как первый звонок цепочки
Начинать нужно не с видео, а с базы данных сайта. Когда клиент оплатил или подтвердил запись, в вашем приложении появляется строка: консультация с номером, специалист, клиент, время начала, плановая длительность, статус. Эта строка и есть источник истины. LiveKit не должен «узнавать» из имени комнаты, что за встреча идёт; он лишь исполняет то, что записано в токене.
Разумно, чтобы название комнаты не было осмысленным и угадываемым. Имя вроде consult-anna-igor или room-101 удобно человеку и очень удобно тому, кто захочет перебирать варианты. Лучше хранить в записи отдельный случайный идентификатор комнаты, не выводящийся из номера консультации арифметически. Но сразу оговоримся: случайное имя — лишь неприятное неудобство для перебора, а не защита. Защита — в том, что войти без выданного бэкендом токена нельзя вообще, как бы хорошо ни было угадано имя. Обещать приватность одним названием комнаты — значит повесить на дверь табличку «не входить» и считать вопрос закрытым.
Путь от записи к подключению выглядит так:
- Клиент открывает страницу консультации в личном кабинете. Сайт проверяет его сессию как обычно, своими средствами.
- Бэкенд находит запись и убеждается, что этот пользователь действительно её участник, а время входа допустимо.
- Бэкенд формирует token для конкретной комнаты и конкретного человека и отдаёт его странице.
- Браузер подключается к медиасерверу, предъявляя этот token. Больше сайт в установлении соединения не участвует.
Обратите внимание на второй шаг: проверка происходит на вашей стороне, до того как токен вообще родился. Медиасервер не будет спрашивать, записан ли Игорь к Анне. Он увидит подпись и содержимое токена и поверит им, потому что подпись сделана секретом, который знаете только вы и сервер.
Что на самом деле лежит в токене
По документации LiveKit, token — это JWT, внутри которого есть identity участника, его разрешения и привязка к комнате, а подписывается он серверным секретом API (LiveKit: токены и права). Три части этого описания и составляют всю модель доступа.
Identity — кто это. Её стоит строить из идентификатора пользователя в вашей системе, а не из отображаемого имени. Имя «Игорь» может встретиться у двух клиентов, а идентификатор пользователя — нет. Отображаемое имя при желании можно передать отдельно, но принимать решения по нему не стоит.
Привязка к комнате — куда можно. Токен Игоря выписан на одну комнату, и в другую он с ним не попадёт. Это ровно то, что разделяет наши две параллельные консультации.
Разрешения — что можно делать внутри. Специалист обычно публикует аудио и видео, клиент тоже, но этим набор не исчерпывается: на вашей стороне решение, нужно ли клиенту право демонстрировать экран, а наблюдателю, например супервизору, достаточно права только слушать и смотреть. Точные названия полей и способ их задания смотрите в документации используемого вами серверного SDK; здесь важна логика: права выдаются минимально достаточные, потому что токен потом уже не переиграть.
Секрет подписи остаётся на сервере
Токен подписывается серверным секретом API, и это определяет главное правило всей архитектуры: секрет никогда не попадает в браузер. Ни в JavaScript-бандл, ни в переменные, которые фронтенд собирает при сборке, ни в мобильное приложение, ни в «временный» тестовый скрипт на странице. Тот, кто получил секрет, может выписать токен на любую комнату с любыми правами и любой identity. Для него не существует «соседней комнаты»: он сам себе её откроет, а заодно представится специалистом.
Практическое следствие: браузер получает только готовый токен и никогда не создаёт его сам. Если на странице есть код, который «собирает токен на лету», значит, секрет уже там, и его нужно считать скомпрометированным. Хранить его следует в окружении серверного приложения, отдельно от клиентского кода, и менять при малейшем подозрении на утечку.
Две машины с разными характерами: приложение и медиаузел
Теперь о том, где всё это живёт. Удобно мыслить двумя ролями. Сервер приложения — это ваш сайт: кабинет, записи, оплата, выдача токенов. Медиаузел — это LiveKit: он принимает соединения, маршрутизирует аудио и видео между участниками комнаты. Ролям требуется разное, и смешение их на одном хосте — не запрет, а решение, которое стоит принимать осознанно.
| Роль | Чем занята | Что для неё критично |
|---|---|---|
| Сервер приложения | Сессии, записи, права, выдача token | Хранение секрета, согласованность данных, привычная веб-защита |
| Медиаузел LiveKit | Приём и пересылка медиа, сигнализация | Сетевая доступность, публичный адрес, процессор и канал |
Главное различие в нагрузке. Сайту консультаций нужны запросы и база; видеоконференция же превращается в постоянный поток, и ресурсы уходят на пересылку медиа, а не на запросы к страницам. Поэтому когда обе роли делят одну машину, всплеск видеотрафика может ударить по кабинету, где человек в этот момент пытается войти на собственную консультацию. Разнесение на два VPS убирает такое соседство. Оговорка: два отдельных VPS сами по себе не дают отказоустойчивости ни приложению, ни медиасервису. Это разделение нагрузок и ответственности, а не резервирование.
Для первого запуска небольшого сервиса обе роли вполне можно начать на одном VPS, если вы готовы наблюдать за нагрузкой. В тарифной линейке MAATRIX ресурсы растут от Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) через Uab (2 vCPU, 4 ГБ) и Heka (4 vCPU, 8 ГБ) до Maat (8 vCPU, 16 ГБ) и Djed (16 vCPU, 32 ГБ). Эти цифры — отправная точка для теста, а не обещание, что столько-то консультаций пройдёт одновременно: ёмкость определяется числом участников, качеством видео и сетью, и честный ответ даёт только замер на вашей нагрузке. Логично начать с тарифа, где приложение и LiveKit работают без борьбы за процессор, а затем смотреть, какой ресурс заканчивается первым.
Сервер, до которого можно достучаться
Установленный медиаузел — ещё не работающий. По документации по самостоятельному развёртыванию, для доступности нужны правильный публичный адрес, домен, доверенный TLS и доступные транспортные порты, а встроенный TURN помогает там, где прямое соединение ограничено (LiveKit: собственный сервер). Переведём это на язык консультаций: клиент с ноутбука в офисе и клиент со смартфона в мобильной сети должны установить связь с одним и тем же адресом, и браузер не должен ругаться на сертификат.
Домен и доверенный TLS нужны не для красоты. Страница вашего сайта работает по HTTPS, а подключение к медиасерверу из такой страницы должно быть защищённым; сертификат, которому браузер не доверяет, превращает консультацию в окно с предупреждением. Публичный адрес важен потому, что узел должен сообщать участникам тот адрес, по которому до него можно добраться снаружи, а не внутренний адрес машины. Ошибка здесь типична и коварна: сигнализация проходит, участники даже «видят» друг друга в списке комнаты, а картинки нет.
Почему открытый HTTPS ничего не доказывает
Именно об этом предупреждает документация по портам: сигнализация и API, ICE по UDP и TCP, а также TURN идут разными сетевыми путями, и открытый HTTPS сам по себе не доказывает, что медиатрафик дойдёт; точные порты зависят от конфигурации (LiveKit: порты и firewall). Для администратора это означает неприятную вещь: проверка «страница открывается, сертификат зелёный» подтверждает только один из нескольких путей.
Поэтому правила файрвола нужно составлять не по памяти и не по чужой статье, а по конфигурации вашего собственного узла: какие порты вы там задали для медиа, включён ли TURN, на каких портах он слушает. Откройте ровно то, что прописано, и ничего «на всякий случай» сверх этого. А затем проверяйте не наличие открытого порта, а реальный звонок, о чём ниже.
Роль TURN проще всего понять на бытовом примере. Некоторые корпоративные и мобильные сети режут прямой UDP-трафик. Клиент оттуда не сможет соединиться напрямую, но сможет через ретранслятор. Встроенный TURN и есть такой ретранслятор: он стоит дороже по ресурсам, зато спасает консультации тех людей, у которых иначе не было бы связи вовсе. Для сервиса, где клиент не обязан разбираться в сетях, это не роскошь.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.
Две консультации одновременно: как выглядят выданные права
Вернёмся к Анне и Борису. Условный пример, не результат измерения и не чей-то реальный случай. В 10:00 в базе две записи: консультация №1 (Анна и Игорь) и консультация №2 (Борис и Марина). Для каждой при создании сгенерирован свой случайный идентификатор комнаты, назовём их условно комната A и комната B.
| Участник | Identity (из ID в вашей системе) | Комната в токене | Права |
|---|---|---|---|
| Анна | специалист, её ID | A | публикация аудио и видео, приём чужих потоков |
| Игорь | клиент, его ID | A | публикация аудио и видео, приём; демонстрация экрана — по решению сервиса |
| Борис | специалист, его ID | B | как у Анны, но в комнате B |
| Марина | клиент, её ID | B | как у Игоря, но в комнате B |
Теперь проверим «атаки», которые приходят в голову первыми. Игорь открывает консоль браузера и подставляет в запрос идентификатор комнаты B. Что получится? Его токен выписан на комнату A и подписан секретом; поменять привязку к комнате он не может, не сломав подпись. Идёт к бэкенду за новым токеном на комнату B? Бэкенд сверит его пользователя с записью №2 и откажет, потому что Игоря там нет. Накопить ещё более странный сценарий: Игорь узнал идентификатор комнаты B и просит бэкенд выписать токен именно на неё. Опять отказ, поскольку выдача зависит не от того, какое имя комнаты запросили, а от того, какой консультации принадлежит пользователь.
Вся защита, как видите, сосредоточена в одном месте: в логике выдачи токена на бэкенде. Она должна выводить комнату из записи консультации, а не принимать её имя от клиента как готовое значение. Если конечная точка выглядит как «дай токен для комнаты X», где X пришёл в параметре запроса и ничем не сверяется, вы построили не доступ, а лотерею. Правильная форма запроса: «дай токен для моей консультации №1», а какая за этим комната — знает только сервер.
Один человек — несколько подключений
Отдельный вопрос — что делать, когда клиент открыл консультацию на телефоне и на ноутбуке. Identity у обоих подключений будет одной, и здесь нужно заранее решить политику: разрешить, заменить старое подключение новым или отклонить второе. Тут нет универсального ответа, зато есть неприятный случай, который стоит продумать: человек перешёл на другое устройство посреди встречи, а старая вкладка осталась открытой. Если политика не определена, собеседник увидит в комнате двух «Игорей» и будет в недоумении. Свойства поведения LiveKit при совпадении identity проверьте на тестовом стенде перед тем, как опираться на них в сервисе.
Сколько живёт приглашение
Токен — это пропуск, а у пропуска должен быть срок. Но срок надо понимать правильно. В документации сказано, что истечение токена применяется к первоначальному подключению и не означает автоматического отключения уже подключённого участника (LiveKit: токены и права). Иначе говоря, токен — билет на вход, а не будильник на выход.
Из этого вытекают два разных вопроса, которые часто смешивают.
Как долго можно входить? Ответ определяется сроком жизни токена, и он должен быть коротким. Бэкенд выписывает токен в момент, когда человек нажимает «войти на консультацию», а не заранее при записи. Тогда токен живёт столько, сколько нужно, чтобы страница успела подключиться, то есть минуты, а не дни. Приглашение, выданное за неделю и пересланное «на всякий случай», — это висящий ключ. Ссылку на консультацию, которую клиент может переслать другому, сама по себе ничего не открывает: она ведёт на ваш сайт, где снова проверяется сессия и право участвовать.
Есть и окно допуска. Логика может быть такой: войти можно за несколько минут до начала и до окончания плановой длительности с запасом на затянувшуюся беседу. Конкретные числа подбираются под ваш сервис; важно, что проверка времени живёт на бэкенде, в момент выдачи, а не на странице, которую пользователь может подправить.
Как завершить встречу? Это совсем другая операция. Раз истечение токена не выгоняет подключённого человека, нельзя рассчитывать, что консультация закончится сама, когда «кончился» токен. Завершение должно быть явным действием вашей системы: когда специалист закрывает приём или истекает отведённое время, бэкенд должен отдать серверной стороне LiveKit команду закрыть комнату или убрать участников. Какие именно вызовы серверного API для этого есть и как они называются в вашем SDK — смотрите в документации используемой версии; для архитектуры важно, что инициатором завершения становится ваш бэкенд, обладающий серверным секретом, а не страница в браузере.
Без этого получается забавная и неприятная картина: консультация формально окончена, запись в базе помечена «завершена», а двое людей продолжают разговаривать, потому что их подключения никто не разорвал. Либо хуже: клиент остаётся в комнате, куда специалист уже не вернётся, а позже в ту же комнату заходит другой по новому приглашению. Поэтому у комнаты должен быть жизненный цикл, согласованный со статусом консультации: создана, открыта, закрыта, и после закрытия новый токен на неё не выдаётся. Именно статус в вашей базе — тот переключатель, который и порождает отказ при следующей попытке входа.
Запись, если она нужна
Не каждая консультация записывается, и многим сервисам запись не нужна совсем: видеоразговор о личном — чувствительный материал, а любая запись добавляет обязанности. Если вы всё же её ведёте, решение надо принять до первой встречи, а не после. Нужно определить, кто имеет право включить запись, знают ли о ней оба участника, где лежит файл, кто его может получить и когда он удаляется. Всё это вопросы политики сервиса и требований, применимых к вашей деятельности, а не техническая настройка; расположение сервера в той или иной стране само по себе не делает хранение записей соответствующим закону, и это следует выяснять отдельно.
Технически же файл записи — это ещё один объект, который нужно привязать к консультации и защитить по тем же принципам, что и комнату: выдавать доступ только участнику этой консультации, и только по проверенной сессии. Отдавать запись по прямой публичной ссылке с угадываемым именем — это повторение той же ошибки с названием комнаты, только в другом месте. Для хранения записей планируйте диск с запасом: видео занимает заметно больше места, чем кажется, а у каждого тарифа объём NVMe ограничен. Точный расход зависит от качества и длительности встреч, поэтому прикиньте его по тестовой записи, а не по общим цифрам. Автоматические резервные копии не следует считать данностью: если записи для вас ценны, способ их копирования придётся организовать самостоятельно.
Проверка из разных сетей: звонок вместо галочки
Итак, токены выдаются верно, домен есть, TLS доверенный, порты открыты по конфигурации. Остаётся самое важное — убедиться, что это работает для людей, а не для вас самих. Ваша собственная проверка почти всегда проходит из той сети, где вы настраивали сервер, а клиенты придут из других. Поэтому после настройки нужно устроить пробную консультацию, где участники находятся в разных сетях.
Разумный набор проверок получается из того, о чём предупреждает документация: у сигнализации, ICE и TURN разные пути, и открытая страница ничего не гарантирует (LiveKit: порты и firewall). Проверьте последовательно:
- подключение из домашней сети и из мобильной, на разных устройствах;
- подключение из сети с ограничениями, например из корпоративной, если среди ваших клиентов такие вероятны;
- что звук и видео реально идут в обе стороны, а не только появляется имя собеседника в списке участников;
- что при блокировке прямого UDP связь устанавливается через TURN, пусть и с меньшим удобством;
- что два одновременных тестовых звонка в разных комнатах не пересекаются и что токен одного не пускает в комнату другого.
Последний пункт — прямая проверка нашего примера с Анной и Борисом. Возьмите два тестовых аккаунта, заведите две консультации и попробуйте всеми способами зайти из одной в другую: подменить параметр, использовать чужой токен, запросить токен для чужой записи. Успешным тест считается тогда, когда каждая попытка заканчивается отказом на бэкенде или невозможностью подключиться. Отрицательные тесты, где всё должно сломаться, ценнее положительных, где всё просто работает.
Если вы выбираете между LiveKit и привычным решением для конференций, поучительно прочесть, как LiveKit и Jitsi Meet сравнивали на практике: там разбирается, что выигрывает и теряет команда при смене инструмента, а для нашей задачи это хороший повод заранее решить, нужна ли вам именно программируемая модель доступа с токенами.
Где разместить такую связку
Для консультаций на сайте VPS с root-доступом хорош тем, что даёт управление всем, о чём шла речь: вы сами настраиваете домен и сертификат, правила файрвола, окружение с секретом и конфигурацию LiveKit. У MAATRIX это серверы на NVMe с процессорами AMD EPYC, а в публичном предложении перечислены локации UK, US, RU и FR; оплата возможна картой РФ и через СБП, а VPS создаётся автоматически через пару минут после оплаты. Наличие нужной ОС и образа в конкретной локации проверяйте в форме заказа. Выбор локации стоит делать не по привычке, а по тому, где находятся ваши участники: задержка между клиентом и медиаузлом заметна в живом разговоре сильнее, чем в загрузке страницы. Тарифы можно посмотреть на странице аренды VPS MAATRIX.
Практичный порядок такой: начать с тарифа, на котором и приложение, и LiveKit работают без борьбы за процессор, провести описанные выше тесты из разных сетей, посмотреть, что заканчивается первым, — процессор, канал или память, — и уже тогда решать, нужно ли выносить медиаузел на отдельный сервер или увеличивать план.
Что видит пользователь, и что делает инфраструктура
Вернёмся в 10:00. Игорь заходит в личный кабинет, нажимает «Войти на консультацию», и в его браузере открывается комната, где его ждёт Анна. Марина в этот же момент нажимает ту же кнопку в своём кабинете и попадает к Борису. Ни один из них не знает, что комнат две, что за ними стоят случайные идентификаторы и что где-то на сервере подписаны два коротких токена. Это и есть признак правильной системы: человек видит только свою консультацию.
А за кулисами каждая роль занята своим делом. Сайт проверяет, кто вошёл и к кому записан, и выписывает пропуск на одну комнату с минимальными правами; секрет подписи не покидает серверное окружение. Медиаузел принимает подключения по домену и доверенному сертификату, пересылает аудио и видео и при необходимости выручает TURN. Бэкенд же, а не браузер, отвечает за то, чтобы в нужный момент встреча закончилась. Они ничего не делят, кроме подписи, и именно поэтому одна консультация не просачивается в другую.