Модель доступа
Две ступени проверки — грант на входе в сессию и билет на каждом сегменте; пять режимов авторизации и точная граница между «не пустили» и «не положено».
Разграничение доступа в SirinVideo устроено двухъярусно. Дорогая проверка — разбор JWT, поход во внешнюю службу, сверка списка принципалов — выполняется один раз, при установлении сессии. Дальше зритель ходит по ссылкам, в которые сервер сам вплёл короткий билет сессии, и каждый сегмент проверяется только им.
Понимание этой границы — половина успеха интеграции: почти все ошибки подключения сводятся к тому, что токен пытаются предъявить там, где ожидается билет, или наоборот.
Пять независимых механизмов доступа#
Их легко перепутать: у них разные директивы, разные коды ответов и разные последствия «не настроил».
| Механизм | Что закрывает | Директива | Если не настроен |
|---|---|---|---|
| Гейт управления | панель /admin (данные), весь /api/v1/*, /metrics |
auth { } |
/api/v1 открыт всем, при старте — громкое предупреждение; /metrics закрыт всегда |
| Авторизация просмотра | HLS, LL-HLS, DASH, MSE-WS, HTTP-TS, WHEP, RTSP, RTMP-выдача, превью, DVR, VoD, экспорт | auth_backend { } |
все потоки открыты всем, предупреждения при старте нет |
| Авторизация публикации | приём RTMP(S) и WHIP | тот же auth_backend { }, действие publish |
публикация открыта всем, кто знает путь |
| Замок домена | браузерный хотлинк на конкретный поток | allowed_referers в блоке потока |
замок снят |
| Лицензионный стоп-кран | вся медиа-выдача при недействительной лицензии | не настраивается | — |
Отсутствие блока auth_backend { } означает, что любой, кто знает или переберёт имя потока,
смотрит его без учётных данных. При старте об этом не пишется ни строки — в отличие от гейта
управления, у которого есть громкое предупреждение. Проверяйте это аудитом конфигурации, а не
журналом: curl -o /dev/null -w '%{http_code}\n' http://media.example.com:8080/<поток>/index.m3u8
должен вернуть 401.
Отдельно стоят ограничители, которые легко принять за авторизацию, но ими не являются:
max_sessions (общий лимит сессий на сервер: по HTTP ответ 503 session-limit, на RTSP-SETUP —
453) и caps в блоке потока
(ограничивает только вкладки встроенного плеера — см. Встраиваемый плеер).
Ярус 1 — грант#
Грант («допуск») — это учётные данные зрителя или публикатора. Он проверяется ровно один раз, в момент установления сессии:
- HTTP-плейлист или манифест (
index.m3u8,master.m3u8,index.mpd); - апгрейд WebSocket для MSE-WS;
- оффер WHEP (просмотр) или WHIP (публикация);
DESCRIBEв RTSP;- команда
playилиpublishв RTMP.
Как предъявляется грант#
| Способ | Формат | Где применим |
|---|---|---|
| Заголовок | Authorization: Bearer <токен-или-JWT> |
HTTP, RTSP |
| Заголовок | Authorization: Basic base64(логин:пароль) |
HTTP, RTSP; принимается режимами internal и http |
| Запрос URL | ?token=<…> |
HTTP, RTSP, RTMP |
| Запрос URL | ?jwt=<…> |
полный синоним ?token= |
Заголовок имеет приоритет над строкой запроса. У RTMP заголовков нет вовсе — там учётные данные едут только в строке запроса URL (см. Авторизация публикации).
Сервер не читает и не устанавливает ни одного cookie на медиа-путях. Схема «пользователь вошёл на сайт, браузер сам носит сессию» здесь не работает: ваш бэкенд обязан выдать токен и подставить его в URL плеера либо в заголовок.
Ярус 2 — билет сессии ?ssid=#
Успешная проверка гранта на плейлисте выпускает билет — компактную подписанную строку (~30–52 символа), которую сервер сам вплетает во все дочерние ссылки отданного плейлиста или манифеста. Клиенту ничего не нужно делать: он просто идёт по ссылкам из плейлиста.
# фрагмент отданного master.m3u8 — ssid подставлен сервером v0/playlist.fmp4.m3u8?sid=2&ssid=MQDP5iZOG6HFegHGfrvjuYpNoxoSfQEAAAAAaoWz-ZBd9pqnRyGy
Билет не хранит свои привязки — они входят в его подпись из самого запроса. Поэтому один и тот же билет:
| Привязан к | Что это значит на практике |
|---|---|
| потоку | билет от cam1 не откроет cam2 |
User-Agent |
скачать ссылку из браузера через curl не получится — 403 |
| классу привилегий | билет живого просмотра не откроет архив |
| протоколу | билет HLS не откроет DASH |
| IP-адресу — необязательно | включается session_keys { bind_ip on; } |
Ярус билетов включается автоматически вместе с любым режимом auth_backend и отключить его
нельзя. Прямой запрос сегмента с ?token= вместо ?ssid= возвращает 401: сегментный гейт
проверяет исключительно билет и ничего не знает о JWT.
Срок жизни билета задаётся в session_keys { ttl … } (по умолчанию час) и продлевается скользяще,
пока плеер опрашивает плейлист, — но никогда не дольше, чем exp самого гранта.
Пять режимов auth_backend#
Режим задаётся единственным блоком auth_backend { type … } — на сервер один бэкенд.
| Режим | Кто проверяет | Что предъявляет клиент | Сетевые вызовы |
|---|---|---|---|
блока нет / type none |
никто | ничего | нет |
type internal |
сам сервер, офлайн, по списку principal |
Basic логин:пароль или непрозрачный токен |
нет |
type hs256 |
сам сервер | JWT на общем секрете (HMAC-SHA256) | нет |
type jwks |
сам сервер, по кэшу открытых ключей | JWT RS256/ES256 от внешнего поставщика | фоновое обновление ключей |
type http |
ваша внешняя служба | что угодно — пробрасывается целиком | POST на каждое установление сессии |
Настройка каждого режима с примерами — Авторизация просмотра.
Три состояния «авторизация не настроена»#
| Конфигурация | Медиа | Зачем так бывает |
|---|---|---|
нет ни auth { }, ни auth_backend { } |
полностью открыто, быстрый путь без единой проверки | стенд, изолированная сеть |
есть auth { }, нет auth_backend { } |
открыто для анонимов | панель закрыта, потоки публичны |
есть auth_backend { type ≠ none } |
закрыто | боевая установка |
Во втором случае бэкенд формально существует, но скомпилирован в правило «публичны все пути»: он нужен только для того, чтобы панель управления могла выпускать свои короткоживущие гранты.
Словарь: действие, привилегия, метка, ресурс#
Действие — что именно делают с потоком:
| Действие | Смысл |
|---|---|
read |
смотреть живое видео |
playback |
смотреть архив: DVR, таймшифт, VoD, экспорт клипа |
publish |
публиковать в поток (RTMP, WHIP) |
api, manage, metrics |
управляющие поверхности; к путям потоков не относятся |
Привилегия — более тонкий срез внутри действия, битовая маска:
| Бит | Имя | Что открывает |
|---|---|---|
0x01 |
live |
живой просмотр любым протоколом |
0x02 |
dvr |
DVR, таймшифт, VoD |
0x10 |
export |
скачивание клипа |
0x20 |
preview |
одиночный кадр-постер preview.mp4 |
Биты 0x04 и 0x08 зарезервированы и медиасервером игнорируются — прав на них выдать нельзя.
Метка — непрозрачная строка на потоке (labels …;), по которой права выдаются сразу группе
потоков. Подробно — Метки потоков.
Ресурс — либо путь потока (cam, live/cam1), либо для видео по запросу синтетический путь
vod/<библиотека>/<ассет>.
Правило допуска#
Итоговое «пустить» складывается так:
пустить = (протокол запроса разрешён токеном)
И (IP запроса разрешён токеном)
И ( путь совпал ИЛИ метка совпала )
И (замок Referer из токена пройден, если он задан)
Три следствия, о которые чаще всего спотыкаются:
- Путь и метка внутри одного разрешения складываются по ИЛИ, а не по И: правило с обоими полями откроет и поток с подходящим путём, и поток с подходящей меткой.
- Запретить меткой нельзя. Метка только добавляет доступ; если у токена есть
path: "*", никакая метка ничего не отнимет. - Разрешение без пути и без меток не даёт ничего.
{"action":"read"}— это не «все потоки», это403. «Все» пишется явно:{"action":"read","path":"*"}.
Что проверяет авторизацию, а что нет#
| Поверхность | Проверяется |
|---|---|
| HLS, LL-HLS, DASH — плейлист и манифест | да, ярус 1 → выдаёт билет |
| HLS, DASH — init, сегменты, части | да, только ярус 2 (?ssid=) |
| MSE-WS, HTTP-TS, WHEP | да, ярус 1 |
RTSP (DESCRIBE) |
да, ярус 1, один раз на сессию |
RTMP-выдача (play) |
да, ярус 1, учётные данные — в строке запроса URL |
RTMP-приём (publish), WHIP |
да, ярус 1, действие publish |
Превью-постер preview.mp4 |
да, ярус 1, привилегия preview; билет не выдаётся |
| DVR, таймшифт, экспорт, VoD | да, действие playback |
/{поток}/embed.html, /admin/**, шрифты |
нет — оболочка страницы публична намеренно |
/metrics, /api/v1/* |
закрыты гейтом управления, а не auth_backend |
Выход в multicast, restream наружу |
нет — клиента нет, авторизовать некого |
| Переадресация к соседу по кластеру | не авторизует сама, а переносит строку запроса с токеном на держателя потока |
Коды ответов: 401 против 403#
| Код | Тело | Причина |
|---|---|---|
401 |
unauthorized |
учётных данных не предъявлено вовсе, либо они отвергнуты (подпись, секрет, iss, aud) |
401 |
token expired |
exp гранта в прошлом либо истёк билет |
401 |
session ticket required |
вариантный плейлист VoD запрошен без ?ssid= |
403 |
forbidden |
подпись верна, но прав на этот поток нет; либо чужой/подделанный билет; либо сработал замок домена |
403 |
license restricted |
лицензионный стоп-кран — не про авторизацию |
429 |
throttled + Retry-After |
серия неудачных попыток с одного IP |
503 |
session-limit |
достигнут max_sessions — не про авторизацию |
Практическое правило: 401 — «предъявите учётные данные или они не приняты», 403 — «данные
приняты, но этого вам не положено».
Тело отказа — короткий text/plain без эха предъявленного значения, а из адреса переадресации
параметры token, jwt и ssid вычищаются перед подстановкой нового билета. Токен не попадает
ни в тело ответа, ни в текст ошибки конфигурации, ни в журнал.
Куда дальше#
- Авторизация просмотра — настройка каждого режима и поведение по протоколам.
- Метки потоков — как выдать права сразу группе потоков.
- Поля токена — полный справочник claim'ов.
- Генераторы токенов на Python — готовые скрипты под каждый механизм.
- Авторизация публикации — RTMP и WHIP.
- Доступ к админке и API — пароль администратора и статические токены API.