TrendVidStream перевела весь свой стек аутентификации на систему с ротацией refresh-токенов, которая обнаруживает украденный токен в момент его повторного использования. Это изменение превратило 30-дневный JWT, позволявший злоумышленнику беспрепятственно действовать, в краткосрочный учетный идентификатор, который мгновенно вызывает блокировку.
Переход был вызван одной утечкой: партнерский SDK кэшировал 30-дневный JWT в открытом виде, злоумышленник извлек его и повторно использовал из другой страны, а единственным средством исправления была ротация ключа подписи — операция, которая разлогинила всех пользователей. Этот инцидент изменил подход компании к безопасности токенов, и теперь эта система обеспечивает работу сервиса потокового видео.
Почему старая модель не сработала
JWT (JSON Web Tokens) — это самодостаточные подписанные объекты, которые позволяют серверу проверять запрос без обращения к базе данных. Это удобство имеет свою цену: если токен действует неделями, его кража дает злоумышленнику доступ на недели. Во время утечки украденный токен оставался валидным до истечения его 30-дневного срока, так как не было возможности аннулировать его досрочно.
Ротация ключа подписи — единственный глобальный способ аннулировать все токены, но она заставляет каждого пользователя входить в систему заново, прерывая работу сервиса и подрывая доверие. Проблема заключалась не в самом JWT, а в использовании единственного долгоживущего идентификатора.
Суть новой архитектуры
Теперь TrendVidStream выдает два типа токенов:
- Access tokens — действуют 15 минут; они содержат разрешения, необходимые для каждого вызова API.
- Refresh tokens — одноразовые токены, которые обмениваются краткосрочным access-токеном на новую пару.
Когда клиент предъявляет refresh-токен, сервер:
- Проверяет подпись и утверждения (claims) токена.
- Проверяет, не был ли токен уже использован.
- Если проверка пройдена, выдает совершенно новый refresh-токен и свежий 15-минутный access-токен.
- Помечает старый refresh-токен как использованный.
Если шаг 2 не пройден — то есть тот же токен появляется во второй раз — сервер расценивает это как сигнал о краже и аннулирует всю «семью» (family) токенов, связанных с этой сессией входа. И жертва, и злоумышленник будут вынуждены войти в систему заново, что пресечет попытку взлома.
Отслеживание «семей» токенов
Вместо того чтобы рассматривать каждый токен как изолированную запись, система группирует их в «семьи», которые создаются при входе пользователя. Каждая ротация создает нового участника этой семьи. Схема SQLite, используемая в продакшене, хранит:
- family_id — стабильный идентификатор всей сессии.
- token_id — уникальный идентификатор каждого refresh-токена.
- generation — счетчик, полезный для отладки.
- used_at — временная метка использования токена.
- revoked — флаг, который при установке отключает всю «семью».
При обнаружении повторного использования флаг revoked для соответствующего family_id устанавливается, мгновенно аннулируя каждый токен, принадлежащий скомпрометированной сессии. Это превращает скрытый захват сессии в высокоприоритетный сигнал тревоги, который отображается в логах.
Три правила реализации, обеспечивающие надежность системы
Сначала проверяйте подпись Злоумышленник может угадать ID токенов и спровоцировать массовые аннулирования, если сервер сначала проверяет «использовался ли он ранее?», а только потом проверяет подпись. Предварительное подтверждение подлинности предотвращает атаки типа «отказ в обслуживании» (DoS).
Предусмотрите «окно возможности» (grace window) Мобильные приложения часто отправляют два запроса на обновление (refresh) почти одновременно, когда срок действия токена истекает. Если сервер будет слишком строгим, второй запрос будет помечен как повторное использование, что приведет к разлогину легитимного пользователя. Небольшой буфер в несколько секунд позволяет «использованному» токену все еще возвращать новую пару, сглаживая состояние гонки (race conditions).
Оборачивайте весь процесс в транзакцию с блокировкой строк Без атомарности два одновременных запроса могут решить, что они первые используют токен, что приведет к выдаче дублирующих refresh-токенов и нарушению гарантии одноразового использования. Транзакция базы данных, блокирующая строку токена, гарантирует, что успешным будет только один запрос.
Итог: Делая refresh-токены одноразовыми и отслеживая их повторное использование, система может превратить каждый украденный идентификатор в сигнал тревоги, защищая пользователей без необходимости принудительного выхода из системы для всей платформы.
