Комментарии 41
Для этого формируется реестр (например в Redis) аннулированных токенов.Для того, чтобы создать такой реестр заранее, надо где-то хранить все выданные токены, а их не хранят. Или придётся создавать запись в реестре по факту прихода запроса с аннулируемым токеном, но зачем это делать, ведь можно просто возвращать ответ о необходимости обновления токена?
Таким образом, это будет не реестр конкретных токенов, а запись о том, что все токены конкретного пользователя, выданные ранее определённого времени, надо считать недействительными.
С другой стороны обычно из-за безопасности хранят все токены. Это нужно для того, чтобы при запросе пользователя о разлогинивании отовсюду, все активные токены сразу отозвать. (Или логика от обратного — если токена нет в базе активных, значит он отозван).
Разлогин это отдельная тема. Бывает нужно ещё иметь возможность разлогинить с одного устройства все сессии. Тут мне кажется целесообразно все же хранить id сессии. И удалять при разлогине. В реестр аннулированных будет внесен такой sid а не userId
Смысл сообщения что сохранять вообще ничего не нужно. Зачем нужно сохранять jti? Что с ним делать дальше?
Мне кажется в стандарте явно не указаны примеры "правильных" использований JWT т.к. это своего рода просто стандартизированный контейнер. А сама реализация, набор данных и т.д. — является инженерной задачей, относящейся к конкретной сфере применения. Есть разные задачи с разными подходами применения JWT и наборами данных.
Например, если речь идёт о коммуникации machine-to-machine (по своей сути микро-сервисы), очевидно, что JWT должны работать на парах ключей, для возможности проверки токена получающей стороной, без уведомления о проверке отправляющей стороны или любой третьей стороны и т.д. Ещё в таких случаях имеет смысл использовать время начала действия токена и наименьшее возможное время жизни токена. Полезная нагрузка токена зависит от назначения сервисов, цели применения токена и т.д.
Если используется не минимальное возможное время жизни токена и/или необходимо иметь возможность отзывать токены, то без реестра в том или ином виде не обойтись. Для таких задач существует jti (идентификатор токена), кстати особых рекомендаций по поводу данных этого поля тоже нет (об этом ниже). Т.е. сами токены хранить не нужно, нужно хранить только их идентификаторы. Важно, что jti не является обязательным полем для всех токенов, решение о его применении принимается для конкретного случая.
Реестр может быть тоже по разному реализован: это может быть Redis с открытым/закрытым доступом, централизованный сервис для проверки валидности токенов (не обязательно выдающий эти токены) или даже открытый, периодически обновляемый файл с перечислением идентификаторов отозванных или активных токенов (в таком случае принимающая сторона может сама проверить отозван ли токен, запросив свободно распространяемый файл с идентификаторами токенов, по принципу проверки подписи через публичные ключи). Очевидно имеет смысл вести реестр именно отозванных токенов (наименьший объём хранимых данных).
Можно например сделать отдельный сервис отозванных токенов с двумя точками входа: точкой отзыва токена и открытый, обновляемый файл с идентификаторами отозванных токенов или точкой проверки наличия в реестре идентификатора. При запросе на отзыв токена необходимо передать либо отзываемый токен, либо идентификатор отзываемого токена с какой-то подписью для авторизации операции отзыва (вот ещё один возможный пример использования JWT для отзыва других токенов JWT).
Если мы рассматриваем JWT относительно аутентификации пользователей, то без сессий нам вероятно не обойтись. В большинстве случаев аутентификация пользователей завязана на сессии и альтернатив нет. В таком случае можно действительно обойтись и без JWT, но это не значит, что использование JWT с сессиями является не верным использованием JWT.
В таком случае имеет смысл сохранять идентификатор такой сессии в поле jti (вспоминаем, что особых рекомендаций по этому полю нет), таким образом jti (а точнее sid) будет идентифицировать не конкретный токен, а скорее группу связанных токенов. Так же в такой реализации у нас уже будет иметься реест действительных токенов (фактически реестр сессий), причём этот реестр по объёму будет где-то по середине между реестром всех активных токенов и реестром отозванных токенов. Для отзыва всех токенов сессии достаточно будет обновить идентификатор сессии или пересоздать её в реесте.
Полезная нагрузка такого токена зависит опять от сферы применения, целей и т.д. Единственное и главное, полезная нагрузка не должна содержать чувствительных данных и желательно не должна содержать часто обновляемых данных (частота обновления выше частоты смены токенов).
В итоге "правильных" вариантов использования нет, есть только рекомендации. Каждый разработчик должен сам придумать правильный вариант использования JWT для своих целей, желательно опираясь на рекомендации.
Мои примеры тоже не претендуют на единственно верные. Действительно для некоторых случаев использование JWT не даёт никаких преимуществ, возможно просто увеличивает трафик. Однако это вопрос уменьшения объёма трафика или применения стандартизированного контейнера.
Правильных вариантов нет, но есть неправильные. Неправильный вариант это такой когда разработчик не может сам себе объяснить почему он использует JWT а не сессионный хэш. Чувствтельные данные в JWT хранить не только можно но и нужно. Параллельно необходимо продумать систему отзыва токена до выхода срока его действия.
Дело в том, что неправильных тоже нет.
В конце своего сообщения я указал пример выбора решения использования JWT не смотря на то, что он увеличивает объём трафика и может показаться более жирным чем тупо id + подпись. Главный аргумент — стандартизованный контейнер, об этом даже в самом стандарте написано. JWT это платформо-независимый контейнер токенов, со статусом стандарта. В base64 все вполне умеют в криптографические подписи тоже и в JSON тоже. Его сделали, специально таким гибким и универсальным ведь в большинстве случаев разработка своего формата токенов является задачей сложной, мало сформулировать формат, нужно его внедрить и поддерживать.
Если речь идёт о ваших внутренних сервисах, которые разрабатываете вы лично или небольшая команда, очевидно id + подпись вам будет использовать выгоднее (правда не всегда). Но если речь заходит о том, что это экосистема разрабатываемая огромным количеством независимых людей и взаимодействие не ограничивается только лишь вашими сервисами, то JWT тут побеждает как минимум имея статус стандарта, гибкостью и уже имеющейся интеграцией и поддержкой.
Потом, в моём примере про хранение сессионного id в jti я не писал, что в токене должен храниться только jti, вы можете вполне туда добавить не чувствительные данные (настоятельно подчёркиваю, что не чувствительные данные) пользователя (например логин, аватар, возможно имя и т.д.), в том числе чтобы снизить нагрузку на базу (это к слову является одним из примеров использования в самом стандарте).
Чувствтельные данные в JWT хранить не только можно но и нужно.
Вот с этим поспорю и очень даже сильно. Чувствительные данные в токене вы можете хранить, если используете специальную версию JWE с шифрование, но не JWT. JWT является открытым форматом и именно по этому в полезной нагрузке подчёркнуто нельзя хранить чувствительные данные. Отзыв токена не влияет на доступ к этим данным. Они опубликованы открытым текстом (если не учитывать b64). И этот токен может сохраняться где угодно и сколько угодно и вы даже не будете об этом знать. Данные в нём соответственно тоже. Именно по этому в JWT должны храниться только не чувствительные данные.
Параллельно необходимо продумать систему отзыва токена до выхода срока его действия.
Не во всех сферах и реализациях это необходимо, опять таки мой пример варианта реализации сервиса отзывов JWT. Я там упомянул про использование JWT для отзыва других JWT.
А мысль следующая в JWT в поле субъекта указываете id отзываемого токена, время жизни создаваемого токена для авторизации отзыва минимальна, но достаточна на совершение операции. Подпись такого токена производится закрытым ключём клиента, имеющего возможность отзывать токены. Публичный ключ клиента сервису отзывов так или иначе известен. Подпись в данном случае совместно с идентификатором клиента (в поле полезной нагрузки) являются аутентификационными данными клиента, запрашивающего отзыв токена. Причём в авторизационном токене хранить jti не нужно, его просто на просто не нужно отзывать. Сама операция отзыва может быть произведена явно только один раз, т.к. на последующие запросы с этим токеном мы можем ничего не делать (в реестре уже будет идентификатор отзываемого токена). Тут уже помогает время жизни авторизационного токена, можно ещё в нём указать время жизни отзываемого токена.
Обращаю внимание реализация зависит от нужд, ни что не является обязательным, если в стандарте явно не указано об этом и не аргументировано.
Чувствительные данные могут быть к разным параметрам. Я говорю данных чувствительных к изменениям.
О том что в стандарте не заданы правильные варианты использования JWT я начал в первых абзацах своего сообщения. То что использование любого средства может быть правильным, неправильным и дико неправильным — мне представляется довольно естественным. Тема с JWT выделяется тем, что примеров дико неправильного использования настолько много, что явно выше 50х50,
Я говорю данных чувствительных к изменениям.
Вот в этом мы разошлись, я под словом "чувствительные" понимал данные, попадание которых в открытый доступ является проблемой безопасности в том или ином виде. Ровно так же про чувствительность данных в полезной нагрузке трактуется в стандарте, там есть ремарка по поводу использования JWE для этих целей.
То что использование любого средства может быть правильным, неправильным и дико неправильным — мне представляется довольно естественным. Тема с JWT выделяется тем, что примеров дико неправильного использования настолько много, что явно выше 50х50
Тут вопрос скорее такой, что считать правильным, а что не правильным. JWT даёт возможность самому исполнять для конкретной задачи конкретный вариант реализации. Причём иногда этот вариант со стороны может выглядеть как "дико неправильный", но это со стороны и смотря только на верхушку айсберга, что внизу вы явно не знаете.
В конце концов то, что сейчас считается не правильным, через несколько лет может оказаться верным.
Для этого лучше использовать Reference-токены.
Достаточко хранить id пользователя и последнее время разлогина. Все токены этого юзера выпущенные до этого времени считать недействительными.
Одно из них, например, что JWT зашифровано (на самом деле только подписано и закодировано base64)
Данное утвреждение либо неверно высказанно либо неверно понято. JWT RFC явно утвреждает обратное — JWT имеет возможность быть зашифровано (JWE). И эта возможность даже вынесена в отдельный RFC
Cryptographic algorithms and identifiers for use with this
specification are described in the separate JSON Web Algorithms (JWA)
[JWA] specification and an IANA registry defined by that
specification. Related encryption capabilities are described in the
separate JSON Web Encryption (JWE) [JWE] specification.
Это и имеется в виду под реестром аннулированных токенов. Важно что это храниться не дольше чем токен и реестр маленький
Или же в JWT хранится только один идентификатор пользователя, профайл которого запрашивается при каждом запросе из базы данных (на самом деле JWT имеет смысл, если Вы хотите уменьшить количество запросов в базу данных)
Уменьшение размера JWT важно для уменьшения нагрузки на сеть. Поэтому и кладут id вместо профиля целиком.
Для высоконагруженных сервисов сеть является ограничивающим фактором в меньшей степени, чем нагрузка на базу данных. Поэтому хранить в JWT идентификатор а потом вытягивать данные из базы — это нецелевое использование JWT. Тем более что подписанный токен будет по объему большим в любом случае, так как размер подписи не зависит от объема данных а зависит от величины ключа. Поэтому токен с нагрузкой в виде uerId или токен с нагрузкой в виде полного профайла будут иметь относительно одинаковый размер, так как большой удельтный вес в этом размере имеет подпись, а не данные.
Если так использоавать JWT, то уж лучше сгенерировать уникальный хэш и с ним ходить — все равно лезем в базу для вытаскивания профайла.
Поэтому хранить в JWT идентификатор а потом вытягивать данные из базы
из кэша.
Каждый сам выбирает какой из способов более оптимальный для его конкретной задачи.
так как размер подписи не зависит от объема данных а зависит от величины ключа
Скорее зависит от применяемого алгоритма подписи. Подпись ведь является по своей сути хешем.
Зависит и от алгоритма и от длины ключа. Я собственно проверял это на практике. Быстрым поиском нашел такое вот обсуждение https://coderoad.ru/6658728/RSA-размер-подписи
Например для ECDSA на кривой в 192 бита при хеше 256 бит в подписи будет 192 бит хеша.
И всё-таки не зависит от ключа на прямую, но зависит от алгоритма подписи напрямую.
Я именно об этом, ведь если мы используем не пары ключей, а просто секретную строчку и один из алгоритмов HS* — длина подписи будет зависеть только от выбранного алгоритма, но ни как не от длины секретной строчки. Т.к. тут у нас только хеш.
Если мы используем уже пары ключей, тут уже сами алгоритмы зависят от длины ключей согласен. Но сама подпись опять таки в глобальном смысле зависит именно от алгоритма подписи. Ведь даже если мы будем использовать одинаковые ключи (очевидно с одинаковой длинной), но разные алгоритмы (мы же можем использовать как RS512, так и RS256 с одной и той же парой ключей), то длинна подписи будет разной вне зависимости от одинаковой длинны ключей т.к. величина блоков этих алгоритмов разная.
Мы же ведём речь о стандарте JWT в целом, а он поддерживает множество алгоритмов подписей в т.ч. и не на ключах. Если выбрать указанный алгоритм "none", то подпись будет равна длине 0 (т.е. вовсе отсутствовать).
И даже при использовании любой пары RS* (RSA) — RS256 и RS512? Это же именно алгоритмы генерации подписи JWT, различия между ними в алгоритмах генерации хешей SHA256 и SHA512, но одинаковый алгоритм RSA. Ладно длина будет одна, но ключи можно использовать одинаковые как для RS256, так и для RS512. И опять таки это не противоречит тому, что я пишу — длина подписи JWT зависит именно от алгоритма подписи JWT, алгоритм подписи JWT может работать как с ключами RS*, ES*, PS*, так и без ключей HS* и "none". Если использовать разные ключи одинаковой длинны в разных алгоритмах с одинаковой хеш-функцией (например SHA256) длина подписи будет разная и зависеть от самого алгоритма будь то RSA или ECDSA.
Опять таки если мы рассматриваем алгоритмы подписи JWT, среди которых есть hash-only HS* (SHA*) и специальный "none" у которых просто нет пар ключей, аргумент про длину подписи зависящую от длины ключа точно так же опровергается. Подчёркиваю я настаиваю именно на утверждении, что подпись JWT зависит от выбранного алгоритма подписи JWT, если это "none" — длина подписи будет равна 0, если это HS* — будет равна размеру хэш-блока, если это RS*, ES*, PS* будет зависеть от длины ключа т.к. длина результата алгоритмов RSA, ECDSA будет зависеть от длины ключа. Но последнее утверждение не применимо к алгоритмам HS* и "none", именно по этому я настаиваю на связи именно от алгоритма подписи JWT. Не стоит откидывать алгоритмы HS* и "none" для них найдутся применения.
К сожалению я далек от алгортмов криптографических. Но их тех воспоминаний когда я ими занимался — минимальный блок, который мы можем получить при алгоритме RSA равен длине ключа. И все блоки кратны длине ключа. наверное в этом дело. Я просто сейчас создал несколько токенов алгоритмом RS256 и выдам результат. Как оказалось подписать этим алгоритмом ключом размером 128 и даже 256 не получается.
Такой токен получается при размере ключа 512
eyJhbGciOiJSUzI1NiJ9.eyJjb3VudCI6MiwiZXhwIjoxNjA3NTU0MDI2LCJpcCI6IjE5Mi4
xNjguNDguNSIsInR0bCI6MTAsImp0aSI6IjlkNWFmZjg1YjBkZWNkOTliOGU5NGE0
YzJmOWZhOGYwZmQyZmQ2YzQzMzJkMTUyZWJjYzA5ZGI2YTUxOGZkNDgifQ.gB
uSqMtN7v_LyGK_6E5GdCoUbvAoaTpA8P9K01_EXNH1Sp6YVnqveSw8TNvzBd5h
mMIN4Bf_UuGhd3QYpG6K4w
а такой при размере 4096
eyJhbGciOiJSUzI1NiJ9.eyJjb3VudCI6MiwidHRsIjoxMCwianRpIjoiZDhiNzViODNkZjg
zY2NlY2Y1NTY3OWI5MTcxN2I5NTA5YWQ2ZTc5NDFmOTBkNjcyOWRkNTI2M2E4
NzAxYmQxOCIsImV4cCI6MTYwNzU1Mzg2NiwiaXAiOiIxOTIuMTY4LjQ4LjUifQ.RkT
PuFo9jKZIQKUSDUEL8qtaNjB1Gm050lj8-Tr1hppOzC7s8OU-
yUt5Ry3KS1OhadCeSq7qIUJmiC9zAN0NQrx1CTuBXbxwf-
qWkknXO7Ob7ejvuvUchTeEDPcNhW5yzTFUp3dbDxKDahyxw7DX7fQBpBYYrFB1
7-s2Jja89BVuU5K6xcJmSCREqj3x326rfebfQSNopxfTzprrLWleM-
3u4JbhXwjMzwrwr8afEi6Tbm-ZeQuCXdwxW8L7I_Lq-
cksUrk0dCp_z4cr32zM6mz6Xuwadg06NCR_hLuuIxC2oyU0Rs6jJsbPCTEup-
uyM5LR3Fw8t8zYMg112GjIHwS0TD9EtWCMRMmytfcpmWMPL8ERHAlAht6v1FC
ohI6Jh5cZZabugVdxhUbmI470arRHusyALeHoE5dj4jNB6vXli3Ce_jrUasEeUE_w3H
93MYSz-msB4Ocr3qMZBsCQBYV2Vcs7mSIuylSKeA4ezjnjePYS_5K3B7U-
HIWkcfRnTan3idMqEZq4BpVSt55jOAlb_5Sg8N9LGfChNkymuHblUWwf0knsacP-
fd1kyClWZtdAS_Ippr8YDjyYAsugHs_IpmW-
AmQbKwYIAc76HMBabc6ZK746PbboJkXKo88nIzZ2AahAQvuFXVReVzuksJzbYakk
_0xPiWhHZLcU6z8
Мы используем jwt не для того чтобы не было обращений к бд, а чтобы сервера были stateless и не было необходимости делать shared сессию
То что вы пишите укладывается в первый кейс который указан в моем тексте.
Демистификация JWT