Аутентификация по токенам, подписанные URL и защита origin

Автор: Николай СапуновОбновлено: август 202614 мин чтения
Содержание статьи +

TL;DR

Аутентификация по токенам и подписанные URL позволяют CDN решать в каждом запросе, имеет ли зритель право скачать манифест, ключ или сегмент – и при этом origin никогда не видит этого пользователя. Математика везде одна: на CloudFront, Akamai, Cloudflare, Google Media CDN, Fastly и Mux короткая строка фактов (URL, срок действия, по желанию IP или гео) хэшируется с секретом и прикрепляется к запросу. Защита origin – вторая половина задачи: единственным маршрутом до источника должен быть CDN, иначе подписанный URL – замок на двери, к которой никто не обязан подходить. Коллизии cache key с параметрами подписи и истечение токена посреди сессии – две самые частые причины «тихих» отказов в проде.

Зачем это нужно

У вас есть стриминговый продукт. Часть контента платная, часть гео-ограничена, часть доступна только залогиненным. Проверка «можно ли этому пользователю?» на origin работает на десять тысяч просмотров в день и ломается на миллион в минуту. Каждый серьёзный CDN решает это подписью URL на edge – и каждая серьёзная команда хотя бы раз делает это неправильно. Эта статья – для инженера, который скоупит такую работу, продакта, сравнивающего вендоров, и архитектора, которому сказали «просто включите signed URLs», не объяснив, что сломается дальше.

Что такое подписанный URL на самом деле

Подписанный URL – это обычный HTTP-адрес, дополненный тремя компонентами: политикой доступа (что разрешено владельцу), временем действия (когда разрешение истекает) и подписью (криптографический хэш, связывающий политику и срок действия с секретом, известным только вам и CDN). Любой может прочитать такой URL, но создать новый без знания секрета невозможно.

В продакшене используются две схемы подписи: HMAC-SHA256 и RSA-SHA256 (в мире JWT – RS256). HMAC использует общий секрет на обеих сторонах – это дешево и быстро; такой подход применяется в Akamai EdgeAuth, Google Media CDN, в рецептах подписанных URL у Fastly, в Cloudflare Stream через signing key и у большинства CDN на базе NGINX. RSA использует пару ключей: вы подписываете приватным, а CDN проверяет публичным. CloudFront и Mux выбирают RSA, потому что это позволяет независимо менять «проверяющего» (публичный ключ в key group у CDN) от «подписывающего» (бэкенда). Обе схемы генерируют непрозрачный blob, прикрепляемый к URL; разница имеет значение только при проектировании ротации ключей.

Рабочий пример. Подписываемый текст в канонической форме – это запрос, который выполнит плеер:

URL path:      /vod/customer42/manifest.m3u8
Expires:       1748185200            (Unix epoch seconds, ≈ 2025-05-25 13:00 UTC)
Allowed IP:    203.0.113.7/32
Customer:      customer42

Эта строка хешируется с использованием секрета по алгоритму SHA-256, результат кодируется в base64. CDN получает запрос, повторяет ту же операцию хеширования с тем же секретом и сравнивает результаты. Если даже один бит не совпадает – неверный путь, срок действия или IP-адрес вне разрешённого диапазона – запрос отклоняется прямо на edge.

Рис. 1. Четыре компонента, общие для любой схемы подписанных URL.

Пять диалектов CDN рядом

Лексика разная, алгоритм один. Эту таблицу команды обычно хотели бы получить в первый день.

CDNФормат токенаАлгоритмРазмещениеГде живёт спецификация
Amazon CloudFrontCanned или custom policy + signatureRSA-SHA1 (legacy) или RSA-SHA256 через key groupsQuery string (?Policy=…&Signature=…&Key-Pair-Id=…) или cookies (CloudFront-Policy, -Signature, -Key-Pair-Id)AWS CloudFront Developer Guide, разделы «Use signed URLs» и «Set signed cookies using a custom policy»
Akamai Adaptive Media Delivery (EdgeAuth)ACL + expiry + nonce + HMACHMAC-SHA256 (default)Query string, cookie или header – настраивается per-propertyAkamai TechDocs, «Enable Token Authentication»
Cloudflare StreamJWT (RS256)RSA-SHA256, генерируется через API token, signing key или Worker bindingКомпонент пути (/<token>/manifest.m3u8)Cloudflare Stream docs, «Secure your Stream»
Google Media CDNDual-token: короткий HMAC для path + длинная cookieHMAC-SHA1 / HMAC-SHA256 / Ed25519Query string + cookieGoogle Cloud «Edge cache origin → signed requests»
Mux (и большинство JWT-first платформ)Подписанный JWT, RS2562048-bit RSA key pairQuery string (?token=<JWT>)Mux «Secure video playback»

Три нюанса, которые потом портят жизнь:

Зарезервированные имена query-параметров различаются. CloudFront удаляет Key-Pair-Id, Policy и Signature перед отправкой запроса на origin, но передаёт любой параметр с суффиксом -PREFIX – это своего рода аварийный выход, если вашему приложению тоже нужен параметр с именем, например, Policy. У Akamai по умолчанию используется hdnts; Google Media CDN применяет edge-cache-token; Cloudflare Stream помещает JWT непосредственно в путь, а не в строку запроса, чтобы URL оставался кэшируемым.

Ловушка IPv6. У CloudFront custom-политики с полем IpAddress – конструкция работает только для IPv4; если на дистрибутиве включён IPv6, проверка IP-адресов тихо перестаёт работать. AWS это документирует, но команды узнают об этом лишь через три месяца, когда первый оператор с поддержкой только IPv6 приходит на iOS.

Алгоритм подписи меняется. Поток CloudFront с доверенными группами ключей использует RSA-SHA256; старый поток с доверенными подписантами – RSA-SHA1 (поддерживается, но устарел). HMAC-SHA1 у Akamai пока ещё используется, но для новых свойств нужно применять HMAC-SHA256.

Подписанный URL или подписанная cookie – для стриминга оба

Для одного скачивания достаточно подписанного URL. При использовании HLS или DASH плеер загрузит один мастер-плейлист, несколько медиа-плейлистов, URI ключа, а затем сотни или тысячи сегментов. Можно подписать каждую ссылку в манифестах по запросу, либо один раз установить подписанную cookie в начале сессии и позволить плееру получить всё содержимое по защищённому пути.

В документации CloudFront прямо указано: подписанные куки – правильный выбор, когда нужно защитить «много файлов» или важно сохранить чистые, кэшируемые URL. Akamai использует схожий подход с session token: access token проверяет сессию, после чего «долгосрочный» session token передаётся в куки или query string на протяжении всего потока. Компромисс заключается в том, что куки требуют поддержки third-party cookies, что может не работать в некоторых браузерах Smart TV и в iOS Safari при кросс-доменном плеере. Гибридный паттерн – подписанные куки для манифестов и ключей, а подписанный URL – для master-файла при инициализации сессии – сегодня является де-факто стандартом в продакшене.

Если вы подписываете URL сегментов по отдельности, подписывайте общий префикс, а не полный URL каждого сегмента. CloudFront и Akamai поддерживают такую практику через wildcard или «ACL» – одна подпись действует на /customer42/title-9000/* в течение часа, плеер не запрашивает новую подпись для каждого сегмента, а ключ кэширования остаётся стабильным. Подпись каждого сегментного URL – вторая по частоте причина падения коэффициента попаданий в кэш после неправильно настроенных cache keys.

Рис. 2. Два потока side-by-side для одной сессии воспроизведения HLS.

Истечение токена и проблема обновления в середине сессии

Токены сделаны короткоживущими специально: утекшая ссылка должна перестать работать за минуты, а не за дни. Для видео на 15 минут срок действия в 30 минут – вполне нормально. Но для трёхчасового концерта или полуторачасового фильма токен закончится посреди воспроизведения, и следующий запрос плейлиста или сегмента вернёт ошибку 403.

Есть три надёжных паттерна. Достаточно длинный срок действия (expiry) – самый простой: устанавливаем срок, который точно пройдёт дольше самого продолжительного сеанса, соглашаясь на чуть большее окно для злоупотреблений. Обновление токена (token renewal) – короткий срок действия плюс обновление подписанной URL с бэкенда до её истечения; в hls.js это реализуется через обработчик события MANIFEST_LOADED с последующим hls.loadSource() на новой URL или через xhrSetup, который автоматически добавляет свежий токен в каждый запрос. Подпись только пути (path-only signature) – подписывается только базовый путь, а отдельный короткоживущий «session token» в cookie отвечает за временные ограничения; подпись на пути остаётся неизменной на весь контент.

Особо стоит выделить переключение битрейта. Баг hls.js №1450 с Akamai, до сих пор цитируемый в сообществе, – это как раз этот случай: Akamai-токен подписывает /master.m3u8 и четыре variant playlist’а, на которые ссылается мастер-плейлист; когда плеер впервые переключается на rendition с битрейтом 5 Мбит/с, он запрашивает пятый variant playlist, URL которого ранее не использовался, а токен на него мог уже истечь. Проблема решается подписанием общего ACL (/title-9000/*) вместо отдельных плейлистов и продлением срока действия токена до конца самой длинной ожидаемой сессии.

Защита origin – вторая часть работы

Подписанный URL на CDN ничего не даст, если любопытный пользователь может постучаться прямо на origin. Замок собирается слоями.

Origin видит только трафик CDN. CloudFront делает это через Origin Access Control (OAC, современная замена OAI) для S3 или через подписанные origin-заголовки на custom HTTP origin. У Akamai – Site Shield с публичным списком IP, который пускает firewall origin. У Cloudflare – Authenticated Origin Pulls с клиентским сертификатом; у Google Cloud CDN – Cloud Armor плюс shared secret в X-Edge-Cache-Token. Принцип общий: network ACL origin отвергает всё, что пришло не с edge CDN.

Mutual TLS между CDN и origin – апгрейд для платного трафика. CDN предъявляет клиентский сертификат, origin его проверяет, посредники не могут выдать себя за CDN. AWS, Akamai, Fastly и Cloudflare поддерживают эту функцию на enterprise-уровне.

Заголовки аутентифицированного источника. CDN добавляет заголовок – CloudFront-Key-Pair-Id, custom HMAC или JWT – который origin проверяет при каждом запросе. Ремень и подтяжки: даже если злоумышленник соскрейпит IP-адрес origin, запросы без заголовка отбрасываются.

Обратный вопрос – «а если сам CDN скомпрометирован?» – как раз и решает DRM и forensic watermarking. Это уже другой уровень защиты; статья рассматривает ситуацию до момента, когда сегмент покидает CDN.

Рис. 3. Защита по принципу «глубокая оборона» – от edge-токена до firewall origin.

Ловушка cache key (или как подписанный URL незаметно снижает hit ratio)

Это самый частый сбой в production с подписанными URL – и он обходится в реальные деньги.

CDN кэширует объект по cache key – детерминированной функции от URL, части заголовков и параметров запроса. Если подпись попадает в cache key, каждый зритель получает свой уникальный ключ кэша для одного и того же объекта: коэффициент попаданий падает с 99% почти до нуля, растёт трафик с origin, а команда узнаёт об этом не из инженерного, а из финансового дашборда.

Три правила спасают. Уберите параметры подписи из cache key. CloudFront по умолчанию делает это для Policy, Signature и Key-Pair-Id; Akamai исключает hdnts; для любого другого вендора или произвольного имени параметра – проверьте и настройте. Для общих ассетов используйте подписанные cookies, потому что они не входят в default cache key. Подписывайте общий путь, а не уникальную URL – cache key у разных пользователей будет одинаковым, так как URL совпадает.

Вторая ловушка здесь – TTL кэша больше срока действия токена. CloudFront спокойно отдаст закэшированный ответ 200 даже после истечения подписи в исходном URL, потому что ключ кэширования не учитывает срок действия. Для платного контента устанавливайте max-age на CDN короче срока действия токена или используйте cookie-сессии, где временной лимит определяется самой cookie.

Типичные ошибки

«Pitfall: подписывать каждый URL сегмента отдельно. Hit ratio падает, счёт за CDN утраивается. Подписывайте общий ACL или используйте signed cookie на уровне сессии.»

Ещё три типичных ошибки. Хранить ключ подписи во фронтенде (он виден в view-source). Устанавливать одинаковый срок действия (expiry) для всех – при успешной replay-атаке злоумышленник может использовать URL в течение всего окна действия. Чтобы минимизировать риски, привяжите срок действия к IP, user ID или device. Пропускать проверку origin, полагаясь на то, что «signed URL и так достаточно» – это ошибка. Без проверки origin злоумышленник может определить источник URL с помощью простого whois-запроса.

Где здесь Фора Софт

Большинство наших стриминговых, OTT, телемедицинских и e-learning проектов начинается с одного и того же разговора: видео-пайплайн уже работает, и его нужно защитить – поставить за paywall, региональную лицензию или ограничить доступом «только клиницисты». Мы настраиваем поток signed URL под выбранный заказчиком CDN, реализуем защиту источника (OAC, Site Shield, Authenticated Origin Pulls или собственный edge-токен), а также интегрируем в плеер логику, при которой истечение токена вызывает алерт, а не жалобы пользователей. Тот же подход используется и в проектах видеоконференцсвязи и видеонаблюдения, где контроль доступа строится по принципу per-room или per-camera, а не per-asset.

Ключевые выводы

  • Signed URL и signed cookie используют одинаковую математическую основу; для HLS и DASH cookie предпочтительнее.
  • HMAC и RSA различаются в первую очередь подходом к ротации ключей, а не уровнем безопасности.
  • Подпись не должна входить в cache key – иначе эффективность CDN падает до нуля.
  • Подписывайте общий путь (через ACL или wildcard), а не каждый URL сегмента.
  • Защита origin – OAC, Site Shield, mTLS или authenticated header – обязательна.
  • Устанавливайте срок действия (expiry) под самую длинную сессию и обновляйте токен в процессе воспроизведения (mid-stream).

Что читать дальше

Призыв к действию

Обсудите с инженером по стримингу проектирование signed URL и защиту origin для вашей платформы · Посмотрите наши кейсы в стриминге, OTT и телемедицине · Скачайте чек-лист hardening для signed URL – одна страница с 12 пунктами конфигурации, которые нужно проверить перед запуском платного стриминга.

Строите такую систему?

Подберём параметры кодирования под ваш контент и посчитаем стоимость доставки до старта разработки.