Pull to refresh

Comments 54

Злоумышленник способен украсть мой секретный ключ, который хранится где-то в секретах и даже разработчикам недоступен, однако он не может украсть session id из куков пользователя. Интересно. Обычно в минусы записывают больший риск, а не меньший.

Не хотите добавлять чувствительные данные в клеймы - не добавляйте. Все равно вы всю информацию о пользователе туда не засунете и вам для большинства операций с пользователем придется общаться с сервисом\базой\кэшем и тд, где эти данные лежат.

Кроме микросервисов существуют еще SSO, интеграции со сторонними продуктами, внешнее апи и еще маленькая телега кейсов, по которым сессия не вариант. И их только больше со временем. Поэтому все стандартные коробочные решения предлагают токены по умолчанию, а с сессией я даже не уверен. Вот лет 15 назад это еще было похоже на выбор. Сейчас даже в монолите чаще всего клиент на каком-нибудь реакте дергает апи с сервиса. Какая там сессия. Вероятно поэтому чаще всего и пищут про плюсы жвт, а не про минусы. Про плюсы лошади перед автомобилем тоже мало пишут, хотя найти их можно.

Сейчас даже в монолите чаще всего клиент на каком-нибудь реакте дергает апи с сервиса. Какая там сессия. 

Что не так с «сессией» в данном случае?

Присоединяюсь к вопросу коллеги @ris58h - в чём преимущество JWT перед sessionId в случае с монолитом на реакте, который дёргает API?

Вот просто для интереса открыл DevTools на этой же странице и посмотрел куки. Там этих id - немерено. Может, там и JWT есть, но они явно обычные идентификаторы не вытеснили.

Я, например, тоже не сильно вижу преимущества JWT перед sessionId вне микросервисов. В классическом монолите для аутентификации пользователей - так точно.

Спасибо за ваш комментарий!

JWT это безусловно хорошая и полезная технология, которая может решать множество задач. В своей статье я не ставил целью подвергнуть её критике, а лишь хотел обратить внимание на те аспекты, о которых некоторые разработчики не всегда задумываются

Существует множество механизмов, позволяющих нивелировать "ограничения" JWT для той же аутентификации , например Proof-of-Possession для JWT (RFC 7800) и еще найдется с десяток и больше вариантов решения вышеописанных проблем

Но даже с этими механизмами нет универсального "инструмента", который одинаково хорошо подойдёт для любой архитектуры и любого сценария. Выбор технологии зависит от бизнес требований, а понимание плюсов и минусов инструмента позволяет принимать правильные решения, по этому я полностью согласен с Вами, что и сессии также не панацея)

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

Про секретный ключ было сказано так как потенциальный ущерб от украденного (или забрутфоршеного, ведь мы имеем на руках текст и как он был подписан - рано или поздно накопим примеров на то чтобы точно угадать ключ которым эту подпись делали) ключа - это полная компрометация всей системы авторизации. Этим могут пользоваться годами абсолютно незаметно, генеря просто раз в месяц один токен с права админа и выгружая все файлы инвойсов от клиентов. Через год можно приходить и просить пару десятков миллионов долларов выкупа.
А все потому что кто-то слишком хипстер и отказался от stateful сессий.

Украденный же айди сессии одного пользователя - приведёт максимум к потере одного аккаунта, или попыткам лишних действий от его имени, но эти действия все ещё будут подчиняться правилам авторизации.
Это также будет несоизмеримо проще и быстрее обнаружить - ведь эээээ сессии сервером отслеживаются - и именно он решает активна ли она ещё или уже нет. Если сервер увидит что например юзер-агент другой стал, или дистанция до отправителя уже другая, или среднее количество запросов в минуту увеличилось, или внезапно больше одного айпи пользуется - её инвалидируют и всё.



А это кстати поможет обезопасить айди сессии в куках:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies

Set-Cookie: id=123213; Expires=*****; Secure; HttpOnly

Не полностью, но от man-in-the-middle уже есть защита - в отличии от незашифрованных пейлоадов токена.




если токен украли, его невозможно отозвать" подаётся как фатальный недостаток, но автор не объясняет, что это ограничение присуще любому "stateless" механизму, а не только JWT. Вместо этого он просто нагнетает, не предлагая контекста или сравнения с реальными альтернативами в таких же условиях.

"Длина токенов": Аргумент про "увеличение нагрузки на сеть" звучит смешно в 2025 году, когда объём передаваемых данных в современных приложениях измеряется мегабайтами, а JWT редко превышает пару килобайт. Это проблема из 2010-х, а не актуальная угроза.

"Уязвимость к атакам": Утверждение, что payload читаем, подаётся как сенсация, хотя это базовое свойство JWT, известное любому разработчику. Автор не объясняет, почему это критично именно для сессий, ведь чувствительные данные можно просто не помещать в токен.

Если вам нужен настоящий разбор JWT, лучше почитать документацию на сайте Auth0 или технические посты на Medium от инженеров, которые реально сталкивались с этими проблемами в продакшене. Эта же статья – пустая трата времени.

Что gpt выдал, то он и написал видимо, даже не вчитываясь

Насчет «длины токенов» соглашусь - если посмотреть, сколько какая - нибудь превью картинка с какого - нибудь интернет - каталога занимает. А вот «stateless” для аутентификации пользователя мало подходит. Особенно в глазах бизнеса. Помню, был такой кейс на работе - тимлид решил внедрить jwt - токены с access/refresh. Я сразу сказал, что будут проблемы с разлогиниванием и выдачей новых прав. Бизнес такой «eventual consistency” не поймет. На что он мне - «все будет в порядке, под мою ответственность». Честно признаюсь, что смеялся над ним, когда встал такой кейс и он попытался объяснить модность jwt токенов бизнесу. Потом переписывал все на сессии с редисом в авральном режиме под угрозой увольнения. «Знаете, словами «а я же говорил» этого не передать»

Ну сделай Рефреш токен 1 минуту и не надо ничего переписывать.

Вроде как основная функция JWT - пихнуть в payload набор прав, который доступен пользователю - обычно их достаточно дорого вычислять. А вызов revocation list - как раз операция дешевая, так как просто проверка по ключу O(1), но можно жить и без него.

А если вычислить права и положить не в JWT, а туда, где лежит revocation list. Останется ли смысл в JWT. Мы же всё равно будем ходить куда-то на каждый запрос, без разницы в revocation list или в user capabilities list.

relocation list можно реплицировать и в нём точно меньше записей, чем было выпущено токенов. А вот ходить за правами ещё куда-то может быть накладно

Права тоже можно реплицировать. Но количество записей действительно будет меньше в списке отзыва, чем в списке прав. Наверное можно как-то сократить список прав, возможно с помощью фильтра Блума. Но это уже надо думать.

Возможно реплицировать только вектор фильтра Блума для отозванных JWT, а при попадании - ходить в лист. Просто, компактно по данным, расширяемо

Поход в revocation list может быть разовым (на API Gateway, к примеру), а далее каждый микросервис в цепочке уже проверяет возможность исполнения своего функционала по ролевой моделе прошитой в токене без похода куда-либо.

Так же, один раз, на внешней границе системы, мы можем разрешить идентификатор сессии в права пользователя.

JWT позволяет делать из солянки общее решение. когда и ваш код и сторонний работает по JWT по общим правилам. Например при интеграции вашего решения в какое-то большее.

В случае монолита - JWT или нет без разницы особо. Т.к. вы владелец и бог всего, то выбор определяется вашим вкусом в прекрасное.

А когда решения разнородны, то лучше чтобы все могли дуржить по какому-то общему универсальному способу и JWT тут просто вот по этому.

Revocation list определенно меньше capabilities list и его проще поддерживать с eventual consistency в каждом сервисе. Можно хоть на флешке принести под роспись.

Почему проще?

И второе, изначально revocation list берётся из желания предсказуемого по времени отзыва токенов. Без него токены протухнут, но не сразу. А если мы сюда добавляем eventual consistency, тем более, если носим его на флешке, то возвращаемся к начальной точке.

Почему проще?

Почему простой список только запрещённых токенов проще чем некая структура пользователь-доступы для всех пользователей? Мне это показалось очевидным, но я могу ошибаться.

предсказуемого по времени отзыва токенов

Если сержантик добегает до сервера за 5 минут, то вот вам и предсказуемое время отзыва. Устраивает ли вас оно - другой вопрос.

Если у нас один сервер, то к чему нам все эти пляски с JWT, eventual consistency и распределённостью.

Revocation list — это множество или список. User cap list — это словарь uid —> data. Они одинаково удобны или неудобны для обновления. Что вы имеете в виду, когда говорите про сложные структуры?

когда говорите про сложные структуры?

Я не писал про сложные структуры. Я писал про проще/сложнее. Список проще/сложнее чем словарь?

Если у нас один сервер, то к чему нам все эти пляски с JWT, eventual consistency и распределённостью.

Не знаю к чему. А к чему вы спрашиваете?

Список не проще и не сложнее словаря.

Сержантик добегает до сервера за 5 минут. Сервер один?

Список не проще и не сложнее словаря.

Этот как посмотреть.

Сервер один?

Допустим, один.

Payload тоже может быть зашифрован, только зачем? Никто же его все равно не украдет, а если украдут, то могут украсть и остальное. Если пустить за свой комп человека с улицы, глупо переживать о том что он откроет панель разработчика и подсмотрит email в access-токене, потому что он может подсмотреть его в другом месте.

Jwt для системы авторизации на сайте действительно не идеален, но в большинстве случаев его недостатки не критичны, а преимущества никуда не исчезают, к тому же это некий стандарт. И он позволяет просто реализовывать например такую логику как «выйти из всех устройств кроме этого».

Хотя он довольно сложен для понимания и для правильного использования. Несколько дней придется поломать голову чтобы полностью проникнуться.

Ни в OAuth2, ни тем более в OpenID Connect не использует JWT для работы с сессиями, в OAuth2 вообще ни слова про сессии. Что не мешает OAuth2 использовать для авторизации как opaque (случайный набор символов, UUID или что угодно) так и JWT. А в OpenID Connect помимо access_token/refresh_token-ов используется JWT для ID Token-а (для передачи клиенту данных о пользователе). И он тоже не используется для сессий (как и для авторизации). Для работы с сессиями в OIDC есть несколько отдельных спецификаций (например, одна из них https://openid.net/specs/openid-connect-session-1_0.html), где описаны процессы работы с сессиями. Я уже молчу про BFF (Backend For Frontend) - шаблон для построения архитектуры приложения.

Короткоживущий AT и httpOnly RT решают большинство описанных проблем.

50 строк на бэкенде и 30 на фронте - вот и всё "усложнение"

RT не воруется, либо добавляется привязка к компу / IP

Для разлогина / отзыва по любому надо что-то делать в БД - можно поставить флаг у пользователя

RT не воруется

Почему вы так решили? httpOnly никак не помешает стянуть файлик с куками напрямую с устройства, например

привязка к компу

Каким образом?

IP

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

Украсть АТ - просто внедрить скрипт (XSS), что часто

Украсть RT - взломать пользовательский комп или сервер вебсайта или провайдера

Разницу не чувствуете в сложности и рисках того и другого?

При защите по IP - происходит комплексная проверка нескольких факторов, не только IP, конечно

просто внедрить скрипт (XSS)

Этот XSS ещё нужно умудриться найти, если он вообще есть. Непонятно, почему вы решили, что это просто

что часто

Почему вы решили что часто? Ну, например, вот вам челлендж — найдите любой XSS на любом из моих работающих сайтов, если «часто», то для вас это труда не составит, да? 🙃

Украсть RT - взломать пользовательский комп или сервер вебсайта или провайдера

У вас сильно нереалистичные представления о безопасности. Достаточно подсунуть юзеру вредоносное расширение «восстанавливающее ютуб» — юзер сам радостно тыкнет в «Дать доступ ко всем сайтам», это НАМНОГО проще чем XSS

Согласно RFC 7519, JWT могут шифроваться. Если хочется скрыть информацию в токене, то кто мешает использовать стандартное для этого решение с зашифрованным payload.

Вообще, в статье путается управление сессиями, стандарты аутентификации и формат упаковки данных.
JWT легко может храниться в куке и содержать sessionId и еще какие-то параметры, как стандартный и удобный контейнер.

А вот стоит ли для авторизации приложения в браузере использовать OAuth2 или что-то другое - совершенно отдельный вопрос. На мой взгляд, для авторизации и аутентификации можно и нужно строить схемы из нескольких токенов, часть из которых хранится в http-only куках на специальном поддомене. Но это уже про довольно сложную схему из трех токенов, частой смены access-token (нужной для обнаружения компрометации, а не для защиты) и не самую тривиальную логик проверки. Но зато это горадо надежнее хранения sessionId

Централизованное хранилище токенов.Некоторые предлагают хранить JWT в базе и проверять его при каждом запросе. Но если всё равно приходится проверять данные на сервере, то почему бы сразу не использовать обычный session ID?

А в чём проблема хранить iat, начиная с которого ключ будет валиден, и проверять только его?

Вы предлагаете хранить время выдачи токена в базе?

Скорее не время выдачи токена, а время отзыва токенов. Если токен был выпущен до отзыва, то он не валиден.

Это не решает проблему централизации, но и JWT полностью хранить не надо.

Читая некоторые комментарии складывается ощущение, что JWT – это развитие идеи сессий и их замена. Но сессии не устарели и их не нужно заменять. С ними всё в порядке.

Идентификатора сессии достаточно в централизованной системе. Там, где информацию о правах требуется получать в одном месте. Предсказуемо, просто и практично.

Если система распределённа по всему, кроме по одной операции – аутентификации, то JWT может помочь избавиться от этой общей точки.

Однако, если мы делаем систему распределённой, значит другого выхода нет. Почему? Потому что распределённая система сложней централизованной при прочих равных. А если нет другого выхода, значит мы согласны на её ограничения. Например, не можем требовать от неё мгновенного бана юзеров.

Если раз за разом, к нашей распределённой системе, предъявляются требования невыполнимые из-за её распределённости – это повод поискать источник конфликта:
– бизнес-задача является централизованной, а реализовали мы её распределённо (зачем)
– бизнес-задача не решается централизованно, но бизнес этого не понимает (донести)

Все ещё не понимаю в чём проблема выпускать токены доступа на 5 минут и обновлять по рефреш. Зачем выпускать долгоживущие токены. Рефрешь при запросе обновления можно отозвать

но что вы будете делать если хранилище оторванных токенов недоступно?

А что делать если хранилище сессий недоступно?

Для большинства читателей это скорее всего неактуально, как и много-регионная установка. Поэтому вы и видите диаграммы и ответы в таком стиле. В подавляющем числе случаев просто добавит заморочек, если сервер один с одной базой данных.

Что-то из комментариев я так и не понял в чем же преимущество JWT для сессий?

Какую то часть нужно всё равно хранить на сервере и проверять (список отзыва как минимум), много данных туда не засунуть (те всё равно придётся подтягивать какой то контекст), на каждый запрос надо проверять подпись.

Основной плюс вижу только в том, что есть куча готовых библиотек и можно не думать - подключил и забыл. С другой стороны всё тоже самое релевантно и к обычным sessionId в куках.

Объясните пожалуйста чего я не понимаю.

Если в Вас все на одном сервере, то все-равно. JWT в основном решает проблему децентрализации и надежности. К примеру, вы можете получить JWT с сервера в Южной Африке, а использовать вообще offline, без интернета, на любом другом сервере на планете. Если даже онлайн, то самому серверу не нужен интернет, не нужно получать информацию о сессии из Южной Африки.

Надеюсь такой сценарий понятен. Естественно, не единственный.

В принципе, JWT похож на аутентификацию сертификатами TLS, я так понимаю

Для отзыва токена можно для каждого юзера в базе хранить уникальный secret (у меня просто uuid4), при отзыве его менять. И все токен не пройдет валидацию

Учитывая, что везде пытаются зачем-то внедрить Keycloak, не вижу способа и причины, как можно вернуться к сессиям, когда аутентификацией занимается отдельный микросервис.

Некоторые пишут в комментариях, что могут Refresh Token украсть - на этот случай можно всю пару менять Refresh и Access, когда Access заканчивается. Тогда если злоумышленник зайдёт - поменяется пара и настоящий юзер уже не сможет зайти. Тогда ему придется снова залогиниться, и когда он залогиниться, то новая пара пришлется AT и RT. И злоумышленнику останется только время до окончания AT. Правда в таком варианте RT нужно в БД хранить. У меня в проекте AT = 2 дня, RT = 30 дней. Но хз насколько это хорошо.

Ваш подход имеет смысл, если вам действительно нужен JWT
Если рассматривать как часто используют JWT (далеко не всегда, от проекта к проекту реализацию отличаются), то это выглядит так:

В БД (лучше Redis для быстрого доступа и возможность удобно использовать TTL) оптимально хранить не сам RT(небезопасно), а его идентификатор JTI (JWT ID), это дает возможность отзывать RT, делая их невалидными без хранения полных токенов. Отзыв RT токена подразумевает под собой невозможность обновить AT.

Сценарий:
Злоумышленник крадет RT+AT пользователя
Злоумышленник может использовать эти токены пока валидны (в т.ч обновлять AT)
Простой повторный вход не решит проблему, потому что выписывается просто новая пара RT+AT (если повторный вход автоматически обнуляет все RT пользователя, это создает другую проблему - пользователь не сможет использовать систему с нескольких устройств одновременно)

простое решение, без использования сложных механизмов:
Пользователь должен иметь возможность "завершить все активные сеансы" и\или конкретный сеанс, НО AT злоумышленника все равно будет валидным в течение срока действия, НО злоумышленник уже не сможешь выписать новый AT по RT, потому что JTI от RT украденный находится к примеру в revocation lists
+ после обновления AT выдается новая пара RT+AT с новым JTI, а старый JTI аннулируется. Это снижает риск повторного использования RT в случае его утечки. *тут также много споров при таком подходе использовать ли allow list для валидных JTI или revocation lists для отозванных JTI, но тут на самом деле зависит от приложения, времени жизни RT\AT итп итд. + allow list позволяет к примеру контролировать "лимит" количества активных сессий на аккаунт

Также я бы рекомендовать сократить срок жизни АТ хотя бы до часа (но в практике моей обычно до 30 минут это бывает). При таком подходи АТ у злоумышленника продолжить "быть", если мы не собираемся при каждом запросе чекать JTI из AT на "забаненость", что я бы не делал, НО можно, к примеру на чувствительные эндпоинты чекать AT (если в нем хранится тот же JTI что и в RT)

Вообщем вариантов много с JWT, от простых до прилично сложных, все зависит от необходимого уровня безопасности приложения\архитектуры\самого приложения, но также не стоит забывать про старые добрые сессии

2 дня очень много для AT. 5-10 минут стандарт.

Всё давно уже сказано до нас.
http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/

Вообще удивительно, как можно всерьёз вот это декларировать: "он stateless, он масштабируем, он безопасен", а главное, суметь убедить кучу народа, что это правда.

Ну какой stateless для сессионных токенов? Если у тебя на самом деле stateless токены и ты честно не хранишь никакого состояния нигде ни в каком виде, как ты их отзовёшь в случае необходимости, которая 100% возникнет?! - первый естественный вопрос любого адекватного человека, который хоть немного в теме. И не важно сколько токенов, один или два access/refresh. С долгоживущим refresh это ещё актуальнее и ещё глупее делать его stateless.

Путаница происходит из-за того, что stateless — понятие относительное. В приложении (в широком смысле этого слова) обычно будут и stateless и stateful части. Например интернет-магазин stateful по своей природе, как приложение, в целом, хотя и может содержать stateless части. Использование stateless подходов внутри задачи, не делают всю её stateless автоматически. Когда что-то декларируется как stateless нужно понять, о какой именно части идёт речь.

HTTP — stateless. Приложения поверх него — необязательно.

TCP —stateful. Приложения поверх него — необязательно. См. HTTP.

"JWT содержит полезную нагрузку (payload), которая хоть и подписана, но не зашифрована. " (с).

Так можно же зашифровать контент (claims).

Спасибо за ваш комментарий!

Вы правы, зашифровать claims можно, либо весть payload целиком, например с помощью JWE

В статье я упоминал, что чувствительную информацию в токене следует шифровать, а не просто подписывать, но основной акцент был на том, что JWT в стандартном виде не предусматривает шифрование, и на практике этот аспект часто опускают

Чтобы избежать недопонимания, внесу небольшие уточнения в текст)

В комментариях не раскрыта тема авторизации (не путайте с аутентификацией). Как в микросервисных архитектурах с JWT принято проверять право пользователя на доступ к определённым ресурсам и действиям?

Sign up to leave a comment.

Articles