Токен авторизации на примере JSON WEB Token

https://proglib.io/p/jwt-for-dummies/
https://proglib.io/p/jwt-for-dummies/

Доброго времени суток, дорогой читатель. В данной статье я постараюсь рассказать об одном из самых популярных (на сегодняшний день) способов авторизации в различных клиент-серверных приложениях - токен авторизации. А рассматривать мы его будем на примере самой популярной реализации - JSON Web Token или JWT.


Введение

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

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

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

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

Еще одно небольшое введение

Прежде чем начать говорить о самом токене авторизации следует упомянуть для каких целей вообще его решили использовать. Поскольку мы знаем, что почти весь интернет так или иначе построен на протоколе HTTP(или его старшем брате HTTPS) и что он не отслеживает состояние, то есть при каждом запросе HTTP ничего не знает, что происходило до этого, он лишь передает запросы, то возникает следующая проблема: если аутентификация нашего пользователя происходит с помощью логина и пароля, то при любом следующем запросе наше приложение не будет знать все тот же ли этот человек, и поэтому придётся каждый раз заново логиниться. Решением данной проблемы является как раз наш токен, а конкретно его самая популярная реализация - JSON Web Tokens (JWT). Также помимо решения вопросов с аутентификацией токен решает и другую не менее важную проблему авторизации (разграничение разрешенных данному пользователю действий), о том каким образом мы узнаем ниже, когда начнем разбирать структуру токена.

Формальное определение

Приступим наконец к работе самого токена. Как я сказал ранее в качестве токенов наиболее часто рассматривают JSON Web Tokens (JWT) и хотя реализации бывают разные, но токены JWT превратились в некий стандарт, именно поэтому будем рассматривать именно на его примере.

JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. 

Фактически это просто строка символов (закодированная и подписанная определенными алгоритмами) с некоторой структурой, содержащая полезные данные пользователя, например ID, имя, уровень доступа и так далее. И эта строчка передается клиентом приложению при каждом запросе, когда есть необходимость идентифицировать и понять кто прислал этот запрос.

Принцип работы

Рассмотрим принцип работы клиент серверных приложений, работающих с помощью JWT. Первым делом пользователь проходит аутентификацию, конечно же если не делал этого ранее и в этом есть необходимость, а именно, например, вводит свой логин и пароль. Далее приложение выдаст ему 2 токена: access token и refresh token (для чего нужен второй мы обсудим ниже, сейчас речь идет именно об access token). Пользователь тем или иным способом сохраняет его себе, например, в локальном хранилище или в хранилище сессий. Затем, когда пользователь делает запрос к API приложения он добавляет полученный ранее access token. И наконец наше приложение, получив данный запрос с токеном, проверяет что данный токен действительный (об этой проверке, опять же, ниже), вычитывает полезные данные, которые помогут идентифицировать пользователя и проверить, что он имеет право на запрашиваемые ресурсы. Таким нехитрым образом происходит основная логика работы с JSON Web Tokens.

https://habr.com/ru/post/336082/
https://habr.com/ru/post/336082/

Структура токена

Пришло время обсудить структуру токена и тем самым лучше разобраться в его работе. Первое что следует отметить, что JWT токен состоит из трех частей, разделенных через точку:

  1. Заголовок (header)

  2. Полезные данные (playload)

  3. Подпись (signature)

funnytorimage.pw
funnytorimage.pw

Рассмотрим каждую часть по подробнее. 

Заголовок

Это первая часть токена. Она служит прежде всего для хранения информации о токене, которая должна рассказать о том, как нам прочитать дальнейшие данные, передаваемые JWT. Заголовок представлен в виде JSON объекта, закодированного в Base64-URL  Например:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Если раскодировать данную строку получим:

{"alg":"HS256","typ":"JWT"}

Заголовок содержит два главных поля: alg и typ. Поле typ служит для информации о типе токена, но как я уже упоминал ранее, что JWT превратился в некий стандарт, то это поле перестало нести особый смысл и служит скорее для целей будущего, если вдруг появится улучшенная версия алгоритма JWT(2.0), которая заменит JWT. Поле alg задает алгоритм шифрования. Обязательный для поддержки всеми реализациями является алгоритм HMAC с использованием SHA-256, или же, как он обозначен в заголовке, HS256. Для работы с этим алгоритмом нужен один секретный ключ, конкретный механизм работы рассмотрим ниже. Для справки можно также отметить, что существует и асимметричный алгоритм, который можно использовать в JWT, например, RS256. Для работы с ним требуется два ключа - открытый и закрытый. Но в данной статье рассмотрим работу с одним закрытым ключом.

Полезные данные

Перейдем наконец к полезным данным. Опять же - это JSON объект, который для удобства и безопасности передачи представляется строкой, закодированной в base64. Наглядный пример полезных данных (playload) токена может быть представлен следующей строкой:

eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9

Что в JSON формате представляет собой:

{"user_id":1,"exp":1581357039}

Именно здесь хранится вся полезная информация. Для данной части нет обязательных полей, из наиболее часто встречаемых можно отметить следующие:

iss - используется для указания приложения, из которого отправляется токен.

user_id - для идентификации пользователя в нашем приложении, кому принадлежит токен.

Одной из самых важных характеристик любого токена является время его жизни, которое может быть задано полем exp. По нему происходит проверка, актуален ли токен еще (что происходит, когда токен перестает быть актуальным можно узнать ниже). Как я уже упоминал, токен может помочь с проблемой авторизации, именно в полезных данных мы можем добавить свои поля, которые будут отражать возможности взаимодействия пользователя с нашим приложением. Например, мы можем добавить поле is_admin или же is_preferUser, где можем указать имеет ли пользователь права на те или иные действия, и при каждом новом запросе с легкостью проверять, не противоречат ли запрашиваемые действия с разрешенными. Ну а что же делать, если попробовать изменить токен и указать, например, что мы являемся администраторами, хотя таковыми никогда не были. Здесь мы плавно можем перейти к третьей и заключительной части нашего JWT.

Подпись

На данный момент мы поняли, что пока токен никак не защищен и не зашифрован, и любой может изменить его и тем самым нарушается вообще весь смысл аутентификации. Эту проблему призвана решить последняя часть токена - а именно сигнатура (подпись). Происходит следующее: наше приложение при прохождении пользователем процедуры подтверждения, что он тот за кого себя выдает, генерирует этот самый токен, определяет поля, которые нужны, записывает туда данные, которые характеризуют данного пользователя, а дальше с помощью заранее выбранного алгоритма (который отмечается в заголовке в поле alg токена), например HMAC-SHA256, и с помощью своего приватного ключа (или некой секретной фразы, которая находится только на серверах приложения) все данные токена подписываются. И затем сформированная подпись добавляется, также в формате base64, в конец токена. Таким образом наш итоговый токен представляет собой закодированную и подписанную строку. И далее при каждом новом запросе к API нашего приложения, сервер с помощью своего секретного ключа сможет проверить эту подпись и тем самым убедиться, что токен не был изменен. Эта проверка представляет собой похожую на подпись операцию, а именно, получив токен при новом запросе, он вынимает заголовок и полезные данные, затем подписывает их своим секретным ключом, и затем идет просто сравнение двух получившихся строк. Таким нехитрым способом, если не скомпроментировать секретный ключ, мы всегда можем знать, что перед нами все еще наш %user_name% с четко отведенными ему правами.

Время жизни токена и Refresh Token

Теперь плавно перейдем к следующему вопросу - времени жизни токена, и сопутствующей этой теме refresh token. Мы помним, что одно из важнейших свойств токена - это время его жизни. И оно совсем недолговечное, а именно 10-30 минут. Может возникнуть вопрос: а зачем такое короткое время жизни, ведь тогда придется каждый раз заново создавать новый токен, а это лишняя нагрузка на приложения. А ответ достаточно очевидный, который включает в себя и одновременно ответ на вопрос: а что делать если токен был перехвачен. Действительно, если токен был перехвачен, то это большая беда, так как злоумышленник получает доступ к приложению от имени нашего %user_name%, но так как access token является короткоживущим, то это происходит лишь на недолгий период. А дальше этот токен уже является не валидным. И именно чтобы обновить и получить новый access token нужен refresh token. Как мы знаем (или если забыли можем снова прочитать в начале) пользователь после процесса аутентификацию получает оба этих токена. И теперь по истечении времени жизни access token мы отсылаем в приложение refresh token и в ответ получаем снова два новых токена, опять же один многоразовый, но ограниченный по времени - токен доступа, а второй одноразовый, но долгоживущий - токен обновления. Время жизни refresh token вполне может измеряться месяцами, что достаточно для активного пользователя, но в случае если и этот токен окажется не валидным, то пользователю следует заново пройти идентификацию и аутентификацию, и он снова получит два токена. И весь механизм работы повторится.

Заключение

В данной статье я постарался подробно рассмотреть работу клиент-серверных приложений с токеном доступа, а конкретно на примере JSON Web Token (JWT). Еще раз хочется отметить с какой сравнительной легкостью, но в тоже время хорошей надежностью, токен позволяет решать проблемы аутентификации и авторизации, что и сделало его таким популярным. Спасибо за уделенное время.

Полезные ссылки

  1. 5 Easy Steps to Understanding JSON Web Tokens (JWT)

  2. JWT — как безопасный способ аутентификации и передачи данных

  3. Securing React Redux Apps With JWT Tokens

  4. Зачем нужен Refresh Token, если есть Access Token?

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 87

    0
    Еще раз хочется отметить с какой сравнительной легкостью, но в тоже время хорошей надежностью

    Лол нет, самый лёгкий и самый надёжный способ — выдать токен просто из рандомных букв, а в базу положить соответствие между этим токеном и конкретным пользователем. Нужны веские причины, чтобы вместо этого использовать что-то сложное наподобие JWT, и они есть далеко не у каждого проекта.


    (А впрочем, есть ещё более лёгкий способ — использовать HTTP Basic аутентификацию и передавать логин-пароль тупо в каждом запросе, но он слишком проблемный и небезопасный и используется редко)

      +1
      Ну хорошо. Вы сгенерировали длиннющую рандомную строку, но что делать если у вас много пользователей онлайн, получается что у каждого юзера такая строка должна быть уникальна. Потому как если будут совпадения, то пользователи с протухшими токенами будут внезапно обнаруживать что они зашли от имени другого пользователя. Следовательно вам как то надо учитывать такие сценарии и протухшие токены где-то хранить чтобы случайно не выдать их другому юзеру. По мере роста количества ротаций токенов совпадений будет все больше.
      Также непонятно как обновлять протухший токен, если у вас только одна рандомная строка.
      Думаю специалисты подробнее вам объяснят, что JWT были придуманы не просто так.
        +2
        если будут совпадения

        Вероятность возникновения совпадения очень близка к нулю. Первое совпадение возникнет через время, превышающее время существования вселенной, даже если генерировать миллионы токенов в секунду (почитайте про UUIDv4 например)


        вам как то надо учитывать такие сценарии

        Не надо, потому что см. выше.


        как обновлять протухший токен

        Обновлять его до того, как он протухнет, очевидно же.


        Думаю специалисты подробнее вам объяснят, что JWT были придуманы не просто так.

        Да, на Хабре другие специалисты уже неоднократно объясняли, что JWT — это очень нишевая штука, нужная далеко не всем. Почитайте другие хабрапосты про JWT и особенно комментарии к ним.

          –2
          Хорошо. Допустим вы генерируете псевдослучайную строку, чтобы быстро авторизоваться (иначе генерация займет приличное время). Но как вы будете противостоять атакам на генератор псевдослучайных чисел?
          Злоумышленник же может зарегистрироваться у вас и получить некую последовательность токенов. Другой вопрос, если у вас регистрация закрыта для посторонних, но там другой разговор.
            0

            /dev/urandom хоть и по сути псевдослучаен, но сделан достаточно умно и инициализируется и периодически обновляется истинно случайными числами — к нему неприменимы атаки на ГСПЧ и его вполне безопасно использовать для генерации любых случайных данных, в том числе связанных с криптографией. (Так по крайней мере в линуксе, но в других ОС тоже есть свои способы безопасной генерации случайных данных)

              +1
              Но как вы будете противостоять атакам на генератор псевдослучайных чисел?

              Очевидно же: используя криптостойкий ГПСЧ, благо таких навалом.

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

                В PHP вообще сессии в языке из коробки, пользуйтесь, куда уж коробочнее ;)

                  +1

                  Ну вот как я упомянул в соседнем комментарии — в PHP и Django из коробки именно рандомные строки, а JWT придётся вручную сбоку прикручивать. Авторизация без аутентификации нужна не всем, распределённость — тоже не всем. В общем, это всё не преимущества и не недостатки, а просто особенности, применимость которых нужно оценивать для каждого проекта отдельно

                0

                Представим себе токен, который состоит из timesamp + 36 байт из urandom. Сколько триллионов токенов мы должны генерировать в день, что получить первую коллизию?

                0
                В большнстве фреймворков есть готовые библиотеки для удобной работы с аутентификацией и авторизацией, которые под капотом как-раз JWT и используют. Буквально недавно делали — IdentityServer на бэке, OIDC на фронте. На настройку и внедрение понадобилось около 30 минут с каждой стороны.
                Со своими токенами, конечно, более гибко, но «это ведь код писать нужно»
                  +1

                  Ну, лично я вот использую PHP и Django — у них под капотом как раз рандомная строка, которая кладётся в HttpOnly куки — для веб-приложений с бэкендом-монолитом самое то. (Хотя для нативных приложений куки это немного странно, но тут мне в целом нечего сказать, не моя область)

                  +2
                  У JWT есть пара преимуществ перед методом хранения строки в бд
                  — JWT пользователя не нужно хранить в базе вообще. При получении нужно лишь проверить его валидность ( то есть что подпись не повреждена, и что он не истек ) и у вас уже есть ключевая информация о пользователе. Причем так как в жвт можно положить что угодно, хоть кеш часто используемых данных.
                  — в случае если один и тот же пользователь хочет залогинится из разных мест одновременно в базе придется продумывать более сложную схему хранения уникальных токенов ( так как одному пользоваетелю будет соотвтестовать не ровно один, но множество токенов ). В Jwt об этом думать не нужно, оно сразу работает так.

                  Из недостатков разве что сложность блокировки утекшено токена, вот тут придется добавлять его в бд, и как-то проверять.
                    –4

                    А еще можно положить в JWT все права доступа и флаг isSuperAdmin… ну что бы вообще не ходить в базу. Ну прям подарок любому хакеру, который рано или поздно доберется до приватного ключа ;)
                    И конечно же один get из базы сессий — это самая основная нагрузка, которую ваше приложение дает на базу.

                      +2
                      Ну прям подарок любому хакеру, который рано или поздно доберется до приватного ключа ;)

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

                      И конечно же один get из базы сессий — это самая основная нагрузка, которую ваше приложение дает на базу.

                      Но это лишний get из базы на каждый пользовательский запрос. Не так уж и мало.
                        0
                        Зачем ему приватный ключ, если он добрался туда где тот хранится то может уже ходить в базу напрямую, ну или добавить свой ssh ключ в систему и спокойно заходить.

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


                        А самое смешное, если программер закроет дырку… то хакер с приватным ключом лишь улыбнется. Это в общем к тому, что ключи ротировать нужно, равно как и задумываться о политиках его хранения и доставки на прод… о чем я еще ни одного упоминания в популярных хабр статьях "какой крутой jwt" не видел.


                        Но это лишний get из базы на каждый пользовательский запрос. Не так уж и мало.

                        Ради интереса — возьмите все окружающие себя проекты. И посмотрите — сколько и какой длительности запросы в среднем на каждый пользовательский запрос. Уверен, этот миленький get, который еще отлично ложится в быстрые базы, и идеально шардируется, покажется ну… комариным укусом.


                        Исключения бывают, конечно… но… исключения, и эти ребята там уже своей головой думают — какой им профит принесет jwt, а какую боль.


                        И, кстати, если мы хотим все же чуть серьезно использовать jwt, то надо задуматься о базе данных отозванных токенов… в которую придется ходить каждый пользовательский запрос ;(

                          0

                          У вас секрет для jwt и пароль от root на сервере и пароль от БД одинаковый????

                            0
                            Разные. Но если кто-то добрался то того места где хранится приватный jwt то значит и до остальных уже не сложно добраться.
                              0

                              Ключ Jwt легче подобрать, имхо. Тем более это можно делать и без вашей системы, т.е. вы даже не узнаете о попытках перебора.

                                0

                                Ну, ключ jwt подобрать сложно (на грани с невозможно) если не было сделано ошибок. Как минимум с выбором размера ключа. А если у вас еще сделана ротация ключей, то можно забыть про этот вектор атаки.

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

                            Ну, во-первых это разные вектора атак. Раскрытие файла, доступ к базе… а я встречал, что ключ в приложение через env передается, это тоже другой вектор.


                            Так что даже наличие sql инъекции не значит, что вы можете что-то записать куда хотите. Т.е. банально база с сессиями может быть совершенно другая и не sql. Так что сессию не пропишете. А спертый пароль еще нужно сбрутфорсить. Да и аутентификация под угнанным паролем может мониторится… хотя бы писаться о факте и реальный админ увидит "вы зашли с ip...".


                            И вообще много что придумать можно. Никто не утверждает, что ключ — это прям ахиллесова пята, а у сайтов с обычными сессиями такой нет. Но факт остается — раскрытие ключа — вполне себе реальная атака, и очень неприятная, особо если вы не задумывались о защите. И последствия такой успешной атаки сильно болезненнее, чем наличие найденной xss или sql injection.

                              0
                              При чем здесь SQL инекция?
                              Я понимаю, что это разный вектор атаки, но в данном разрезе это не имеет значения.
                              Допустим вы спроектировали два приложения, одно использует ключ для подписи токена, другое пишет в базу «случайные» строки.
                              Чтобы условия были равными, мы принимаем как исходные данные идентичное состояние безопасности сервера и его хотя бы минимально грамотную настройку.
                              Чтобы утянуть с сервера ключ, вам потребуется получить права минимум приложения которое имеет права этот ключ читать, т.е. права пользователя под которым этот код исполняется.
                              Если вы получили права пользователя под которым исполняется код, вы сможете подключится к любом хранилищу в который этот код пишет с аналогичными данному коду правами.
                              Если вы можете подключится к хранилищу с правами кода, значит вы сможете записать в это самое хранилище нужную вам аутентификационную строку без прохождения аутентификации.
                              Аналогично имея права кода вы можете прочитать все запросы к коду и просто «подсмотреть» логины и пароли в запросах к этому самому коду прилетающих.

                              Т.е. если злоумышленник имеет возможно компрометации ключа, то ваша система безопасности скомпрометирована в любом случае.

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

                                Утечка ключа и полноценные права на сервере — все же разные вещи. Не обязательно получить RCE, что бы достать ключ, это может быть просто раскрытие содержимого файла… какой-нибудь сломанный readImage и т.п.


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

                                  0
                                  Можно узнать зачем у вас readImage в сервисе аутентификации пользователя?
                                  Так у вас может быть ломанное еще что-то позволяющее писать в базу или читать из нее. Ключ в крайнем случае можно даже на токене хранить и тогда его вообще не возможно будет прочитать.
                                    0

                                    Какой сервис аутентификации, о чем вы? JWT сплошь используют программисты, которые знают слова "симфони, бандл, композер интсталл, jwt это круто, экономим запрос в базу". И их учить нужно, а не говорить им про какие-то мифические сервисы аутентификации.

                          0

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

                            +1
                            Из недостатков разве что сложность блокировки утекшено токена, вот тут придется добавлять его в бд, и как-то проверять.

                            А что делать в ситуации когда пользователь сменил пароль и его нужно разлогинить со всех устройств? Пусть даже не со всех, с текущего.
                            Как только Вы сохраняете JWT в базу он превращается в обычный токен авторизации и теряет свои преимущества.

                            Или как обновить информацию в токене, если пользователю поменяли права доступа с admin на user, например?

                            Вот хорошая статья на эту тему cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions
                              0

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


                              Т.е. в целом проблемы решаются. Но, тяжело, сложно, накладно… и получается много дешевле плюнуть и юзать сессию с единым сервисом валидации токена.


                              По-этому и правда, stop using jwt fot session, всячески поддерживаю. Те, кто собирается все же это делать — они должны все просчитать, а не просто начитаться статей в интернете какой jwt крутой.

                                0

                                Сам владелец токена то не поменялся и в месте с этим его авторизация

                            0

                            JWT хорошая технология, использую её но не так часто как хотелось бы. Хочу попробовать ее с hasura но мешает отсутствие готового JWT сервера с базовым набором: регистрация нового пользователя/права доступа, роли/выдачи токенов после идентификации с готовой веб мордой, чтобы закинуть на отдельный домен/виртуалку/докер. Но что-то внятное не могу найти по этой теме.

                              0
                                0

                                Спасибо за наводку! Буду пробовать

                                  +1

                                  Не уверен, что это хорошая идея. Сначала я тоже обрадовался, задолбало уже на каждом проекте переизобретать собственный сервис аутентификации/авторизации, но потом я посмотрел на список CVE (уязвимостей) для Keycloak и что-то мне расхотелось его дальше изучать — разработчики такого сервиса не должны допускать столько уязвимостей.

                                    +1

                                    И вам спасибо, за ценный опыт! Попробую Django для управления пользователями и простой view, который будет проверять токен и возвращать hasura заголовок с id пользователя и ролью (группой).

                                      0

                                      Кейклоак дефакто очень распространен если вы в мире OAuth2. Несмотря на "список уязвмостей" — хорошо что он есть потому что это проект кого-то интересует. Например Redhat. Другое пюроектры тоже иногда вдруг дерявые -просто это мало кото интересует…
                                      Так что разберитесь с ним. Многое поймете. А потом уже будете решать.

                              +4
                              вопрос: а что делать если токен был перехвачен

                              вопрос: а что делать если refresh токен был перехвачен

                                0
                                Зависит от реализации. В двух словах — повторная аутентификация с опцией сброса всех существующих refresh токенов пользователя.
                                  0

                                  А… а я думал, что предусмотрен super-refresh-token, что бы рефрешить рефреш токен ;)
                                  (ps: это, как и вопрос, был легки сарказм, причина его чуть ниже в соседнем треде)

                                +4
                                А если у клиента угнали refresh token? Как отловить злоумышленника, который получает новую пару?

                                Почему никого не смущает получение злоумышленником доступа на 10-30 минут…

                                *Пользователь* — Меня взломали! Сделайте что-нибудь!
                                *Разработчики* — Да это всего на 30 минут. Не переживайте!
                                  +2
                                  Вот я тоже сколько раз ни читал эту двухуровневую структуру так и не смог понять чем она лучше обычной, одноуровневной.
                                  Почему если мы предполагаем что могут угнать обычный токен, то почему-то не могут угнать рефреш?
                                  И чем угон на полчаса более безопасный чем угон на месяц. Да за полчаса можно любые данные слить обычно, ну или самые критичные. Ну или просто угонять его из раза в раз, и сливать данные постепенно ( раз уж кто-то предполагает что это сделать возможно ).
                                    0

                                    Потому что это не от угона ;) Это по сути logout такой. Чем короче живет основной токен, тем быстрее будет логаут (со стороны сервера я имею ввиду… бан, например, смена пароля или принудительный логаут всех сессий). Или тем меньше размер базы отозванных токенов.

                                      0
                                      Преимущества в безопасности и гибкости для масштабирования. Идея не нова (хранение данных на клиенте используя цифровую подпись) и была еще на заре веба. Сейчас просто это красиво упаковали. Незаменимая вещь в децентрализованных системах, а также позволяет авторизовывать ваших пользователей третьей стороной by design.
                                        0

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

                                          0
                                          То есть реально аутентификация это рефреш-токен, а временный токен это просто кусок закешированных данных о пользователе?
                                            0

                                            Любой токен — это аутентификация. Т.е. по токену мы определяем, что запрос именно от этого человека. Просто в случае рефреш токена добавляется дополнительные проверки состояния пользователя в системе. Это, наверное, ближе к авторизации — мы знаем, что он Вася но должны проверить — а что он может делать (забанен или нет) и выдать или нет новый токен.

                                              0

                                              В отношении JWT — это просто текст в формате JSON с криптографической подписью.
                                              Где и для чего он будет использован стандарт не определяет.
                                              Это как сказать что печать удостоверяет паспорт а паспорт удостоверяет личность.
                                              Но печать можно использовать и для подписания договора или там орехи колоть.

                                              0

                                              Имеетсв в виду что есть некоторый механизм который Вы предъявляете серверу для получения доступа (например пароль, СМС) Пройдя это механизм, Вы получаете токен которым можете обновлять свой доступ без прохождения полной процедуры. Но это скорее философия. На практике нужно понимать две вещи
                                              1) JWT — это просто JSON с подписью который который как тот горшок в котором можно хранить не только мед а и все что угодно
                                              2) access токен хранит в себе данные для доступа к ресурсам например там может быть указано role=admin и у вас есть доступ на уровне админа (не нужно в каждом запросе лезть в базу и проверять права). Но поскольку кто-то в базе может Вас заблокировать то нужно периодически сверять токен для этого access токен делают срочным
                                              3) Псокольку access token истек нужен еще один токен чтобы обновить access токен без прохождения процедуры идентификации


                                              Когда разработчик понимает какая функция у каждого и токенов, сроков действиая токена и т.п. — не возникает споров на ровном месте. В стиле "А я Вам говорю что..."

                                            0
                                            Первое, это такой вариант логаута. Второе, рефреш токен одноразовый, т.е. его компрометация очень быстро выявляется. Третье, рефреш токен, может быть не только по времени, но и например по смене ip адреса.
                                              +1

                                              Что если пользователь честно использовал рефреш токен, но у него в этот момент глюкнул интернет и он не получил новый рефреш?

                                                0

                                                Где вы встречали реализацию одноразовых рефреш токенов? Все, что я видел — там никакой речи об одноразовости. Рефреш токен — это долгоживущий токен.

                                                  0

                                                  Долгоживущий != многоразовый. При каждом запросе с refresh token можно выдавать не только access token, но и новый refresh token.


                                                  Безопасная реализация OAuth2-сервера это нетривиальная задача, потому что "фреймворк OAuth2" оставляет слишком много свободы в выборе вариантов реализации на совести разработчиков, и большинство из этих вариантов имеют проблемы с безопасностью. В результате безопасная реализация refresh token превращается в целый квест:


                                                  • OAuth-сервер должен ограничить выдачу refresh token только теми OAuth-клиентами, которые явно при регистрации указали, что он им нужен, и которые имеют client secret (т.е. не являются веб/десктоп/мобильными-приложениями без бэкенда и имеют возможность хранить свой client secret в тайне).
                                                  • OAuth-сервер должен явно показывать юзеру при выдаче доступа клиенту в списке scope, что этот клиент получит refresh token и сможет сохранять доступ к аккаунту юзера неопределённо долгое время.
                                                  • OAuth-сервер должен некоторое помнить выданные ранее refresh token даже после их смены, чтобы иметь возможность определить факт утечки токена (по повторному запросу с уже сменённым refresh token) и инвалидировать все выданные ранее.
                                                  • OAuth-сервер и клиент должны реализовать небольшое расширение протокола, которое защитит клиент от потери нового refresh token в момент его смены: клиент получив новые refresh token и access token должен сделать дополнительный запрос к OAuth-серверу используя новый access token, подтвердив тем самым получение обоих токенов, а сервер не должен выполнять замену старого refresh token новым до получения этого запроса. Это даёт возможность клиенту повторить запрос со старым refresh token если предыдущий запрос провалился и защищает его от потери refresh token. С другой стороны, это даёт возможность укравшему refresh token хакеру некоторое время пользоваться им незаметно, обновляя access token — но только до момента пока настоящий клиент не обновит access token. Но, учитывая что и настоящий клиент может отказаться от реализации этого расширения протокола и не делать второй запрос (если его не заставлять), это может превратить долгоживущий токен в многоразовый. Поэтому OAuth-серверу имеет смысл реализовать дополнительные ограничения — либо ограничить время ожидания дополнительного запроса подтверждающего смену refresh token, либо собирать постфактум информацию об access token использовавшихся в другие сервисах, и как только среди них будет замечен access token выданный при ещё не подтверждённой смене refresh token — инвалидировать предыдущий refresh token.

                                                  Что до "где встречали" и "я видел" — к сожалению, большинство OAuth-серверов довольно далеки от состояния "безопасная реализация OAuth", потому и не встречали. (Была многолетняя история с фейсбуком, который отказывался закрывать известную уязвимость в своей реализации OAuth просто чтобы не ломать совместимость с существующими клиентами.)

                                                    0
                                                    При каждом запросе с refresh token можно выдавать не только access token, но и новый refresh token.
                                                    Не можно, а нужно.
                                                    0

                                                    Долгоживущий и одноразовый — это разные измерения.
                                                    Долгоживущий определяет время жизни сессии от логина до разлогина.
                                                    Одноразовость определяет что его можно применить лишь однажды. Как правило лимит (но не 1)


                                                    Но зачем, спрашивается, обновлять долговременные токены?
                                                    Ответ совсем не тот который часто приводят. Долговременные токены обновляют при каждом запросе чтобы время его действия продлевалось не от времени выдачи долговременного токена а от времени последней активности клиента.
                                                    Я на сходную тему недавно сделал сообщение https://habr.com/ru/post/532130/
                                                    Суть в том что в народе ходит просто фольклор о JWT. Все знают что токана должно быть два, что их нужно ротировать, что нужно что-то хранить в базе данных — но на вопрос "А зачем?" — отвечают как-то туманно: "Так делают. Это для защиты". В результате из-за непонимания функции токенов происходят неверные решения и споры там где их не должно быть, там где задача 2 * 2.

                                                      +1

                                                      Разные. И вопрос был о реализации одноразовых токенов. Ибо это очень нетривиальная реализация. Если посмотрите сообщение выше от powerman — там есть приблизительный алгоритм реализации одноразового рефреш токена. И не удивительно, что мало кто такое реализует даже у oauth провайдеров. А уж для "домашнего использования" — не будет такое никто делать. Я уверен, что многие даже и не хранят эти рефреш токены.

                                                        0

                                                        Кто и зачем их должен хранить?

                                                          0

                                                          А как реализовать отзыв конкретного рефреш токена без его хранения? Как отобразить список пользовательских сессий без хранения рефреш токена? Ну и если говорить об одноразовых токенах — как обеспечить его одноразовость и защиту от потери при смене — если не хранить их.

                                                            0

                                                            С пользователем связываются его логины. Во всех рефрештокенах от одного логина хранится значение логин идентификатор. При перевыдаче рефрештокена если логин прибит то токен не переиздается. Если уж кому то хочется с одноразовыми работать что нмв редкий кейс. рядом с id логина можно хранить id последнего выданного токена. Чем гарантируется что после перевыдачи токена старый уже не будет использоваться. В конце концов хранить можно все. Например все параметры входящих запросов и ответы сервера. Только весь вопрос с какой целью это делать.

                                                              0

                                                              Завершение конкретной сессии. Вы, как пользователь, не хотите прибивать свой логин. Вы хотите убить неактуальную сессию. Да просто хотите список сессий — кто откуда логинился и когда последний раз была активность (использование рефреш токена).


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

                                                              Что такое "id последнего выданного токена"? Это id где-то хранится?

                                                                0

                                                                Если нужно организовать одноразовость то рядом с ид логина. То что вы описывает хочу знать где и когда был выдан последнего й токен это уже относится не к системе доступа а к системе логирования. Хранить в базе данных можно все и вся. Гугл так и делает. Важно понимать зачем это делается и что за это нужно платить.

                                                                  0

                                                                  Давайте без патетики. Что нужно хранить в jwt рядом с логином, что бы обеспечить одноразовость рефреш токена. Не понимаю, расскажите алгоритм.

                                                                    0

                                                                    Да я его уже три раза описал в предыдущих комментах.
                                                                    Логин формирование ид логина и ид рефрештокенах токена сохранение их в строке таблицы с ид пользователя.
                                                                    Приходит токен на обновление проверяем ид логина если не прибили проверяем ид токена если совпадает выдаем новый токен с новым ид и апдейтим таблицу по ключу ид логина новым товаром.
                                                                    Приходит еще раз кто то со старым токеном на обновление у нас уже ид не совпадает и со старым ид второй раз токен не получаем

                                                                      0

                                                                      Т.е. хранить рефреш токены в базе. Или их id. О чем была и речь — рефреш токены нужно хранить в базе.
                                                                      И к слову, раз вы каждый раз на рефреше ходите в базу — зачем вообще что-то "хранить в рефреш токене". Рефреш токен может быть просто случайной строкой, а не jwt, ибо все-равно происходит сверка по базе.

                                                                        0

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

                                                                          0

                                                                          Это вы меня спросили "Кто и зачем их должен хранить?" когда я сказал о хранении рефреш токенов в базе.


                                                                          Тут опять я нахожу неполную ясность в смысле токенов. В базу не ходят для access токена.

                                                                          Где кто-то говорил про access токены и базу? Речь шла про рефреш токен.

                                                                            0

                                                                            Ваш вопрос зачем хранить что то в токене. Если все равно сверяется с базой. Согласен. Рефреш токен может быть строкой произвольной. Все равно в базу. Это если подходить математически. Но есть другой ракурс. Проверить подпись вебтокена можно на уровне nginx, haproxy ещё до входа в приложении и без обращения к базе данных. Простой математикой. Этим мы отсекаем DDoS атаки когда нам с произвольной строкой будут подсовывать сервис и мы с этими невалидными токенами будем класть нашу базу данных

                                                                              0

                                                                              Это не так-то просто. Токен вполне может быть валидным, валидными токенами тоже можно DDoS устраивать. Полноценно проблему DoS реляционной БД обычно решает кеш данных о токенах из этой БД, напр. в Redis или прямо в памяти сервиса аутентификации.

                                                                                0

                                                                                Посмотрите здесь https://habr.com/ru/post/531004/ описан кейс как на уровне прокси (в данном случае Haproxy) можно погасить DDoS. Да счетчики в Redis или в таблицах Haproxy. Но это краткосрочные счетчики фактически пришедших токенов, которые сбрасываются через секунды или минуты.

                                                                                  0

                                                                                  Эм… а какая разница, написать несколько строк кода в своём сервисе на знакомом языке, или примерно столько же (хотя на первый взгляд кажется, что всё-таки чуть больше) строк на Lua для HAProxy, делающих то же самое?

                                                                                    0

                                                                                    Если делать на ooenresty это работает очень быстро. Про haproxy не знаю наверное тоже нормально. Сервис же может быть достаточно тяжеловесные. Например если php то это затратно так ка поднимается новый процесс. Если nodejs то там единственный тред и его занимать криптографической математикой не совсем оптимально. Защита от DDoS более надежна если на уровне вне приложения. Тогда можно менять оперативно различные параметры. В данном случае задаче не только проверить правильный ли токен и сколько раз он был использован. Задача оставить сервис работоспособным при наличии трафика от злоумышленников который приложение не может выдержать

                                                                                      0

                                                                                      Ясно. Ну, я на Go пишу, так что взгляд прямо противоположный: в своём сервисе делается вообще без проблем, а привязываться к HAProxy… сегодня перед сервисом он, завтра Træfik, послезавтра AWS ELB. Плюс дополнительные вопросы как этот код на Lua тестировать, как собирать и репортить связанные с ним метрики, как контролировать не отвалилась ли защита при очередном деплое…

                                                                                0

                                                                                Отсекаем ddos… в целом оно работать то будет, но очень однобокое решение получается. Аутентификацию по логину и паролю в никаким JWT не прикроете. И еще кучу ендпоинтов. Городить JWT в рефреше только для ddos… ну если вас прям сейчас атакуют на этот ендпоинт, то можно скостылять. Если на перспективу, то нужно как-то более глобально подходить к защите от ddos.

                                                                                  0

                                                                                  Endpoint-ы которые дёргаются без аутентификации (вроде логина) обычно прикрыты чем-то другим — от капчи до rate limit по IP и/или логину. А вот всё остальное так не прикроешь уже, отсюда и нужда в редисе для защиты реляционной БД. Но Вы, безусловно, правы в том, что в первой версии это никто обычно не делает, ждут атаки, и уже потом, когда бизнес осознал что это необходимо, добавляют.

                                                            0

                                                            Все реализации нормальны Auth server конечно знают выданные ими refresh token и access token ;)

                                                            0

                                                            У refresh token меньшая вероятнось бык угнаным. Он не посылается никуда кроме Auth Serverу. А вот Access Token шлют ressource serverам — тут много что может случится.

                                                            0

                                                            В кейклоке, при рефреше каждый раз новый...

                                                            0

                                                            Кто и где закрепил что 1) рефреш токен должен быть обязательно и 2) он должен быть одноразовый. Я Вам скажу если он будет одноразовый то сессии будут постоянно прерываться потому что особенность протокола http такая что могут быть запросы полученные сервером и отправленные как 200-е, но не полученные клиентом. То есть одноразовым этот токен не может быть никогда кроме может быть приложений с самым высоким уровнем защиты.

                                                          0
                                                          Отловить никак, только инвалидировать все refresh токены пользователя. Доступ на 10-30 минут смущает, можно снижать время жизни, но всегда есть инструмент прекратить атаку, как я описал выше. Смущает много большее время доступа при использовании стандартного механизма сессий. Иногда это могут быть и годы, что предпочтительнее, так как целесообразно проводить атаку, когда пользователь не пользуется сервисом.
                                                          +1
                                                          Все постояннос равнивают JWT с сессиями, забывая (или не зная) откуда ввобще оно взялось. JWT часть OAUTH спецификации, которая рассчитана на мультисерверную среду. Там есть сервер авторизации — который выдает токены и сервер ресурсов — который использует токены. В спецификации это разные сущности, и им друг с другом не нужно общаться. Попробуйте не общаться, если у вас просто идентификатор сессии.
                                                          Отсюда можно сделат вывод — если у вас монолит — вам не нужно все усложнять — используйте сесиии. Если у вас мультисерверная среда — не придумывайте велосипед — используйте OAUTH и JWT токены.
                                                            0

                                                            Вот хорошо начали, но вывод не совсем верный. Ведь oauth то расчитан не просто на мультисерверную среду. А когда разными ресурсами этой среды управляют совершенно не связанные компании, и одна из них — центр аутентификации.
                                                            И в такой схеме jwt хорош, да.


                                                            Например, хостите видео на cdn и хотите выдавать уникальные ссылки своим пользователям, да еще и ограниченные по времени действия. JWT идеален, если CDN поддерживает. Грузите туда публичный ключик, подписываете приватным… отлично.


                                                            А если это ваш хостинг, то волне возможно, что проще и безопаснее передать id сессии и настроить единый центр аутентификации внутри периметра.

                                                            +1
                                                            «Поскольку мы знаем, что почти весь интернет так или иначе построен на протоколе HTTP(или его старшем брате HTTPS) и что он не отслеживает состояние» — TLS протокол имеет состояние. Выдержка из статьи: spring.io/guides/tutorials/spring-security-and-angular-js
                                                            You can’t have a secure, stateless application. So where are you going to store the state? That’s all there is to it. Rob Winch gave a very useful and insightful talk at Spring Exchange 2014 explaining the need for state (and the ubiquity of it — TCP and SSL are stateful, so your system is stateful whether you knew it or not), which is probably worth a look if you want to look into this topic in more depth.
                                                              0
                                                              TCP and SSL are stateful, so your system is stateful whether you knew it or not

                                                              Справедливости ради, stateless протокол можно построить поверх stateful и наоборот: stateless-протокол HTTP работает поверх stateful-протокола TCP, который работает поверх stateless-протокола IP, и так далее.

                                                              0

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


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


                                                              Понятно, что если ваше приложение уже получает JWT от стороннего сервиса вроде гугла то придётся использовать JWT (только сначала убедившись, что текущая реализация не имеет известных проблем с безопасностью, и перепроверив что способ которым вы используете библиотеку для JWT не создаёт другие известные уязвимости). Но вот если вы пишете новый сервис и в нём реально необходим не обычный токен в базе, а нужно, например, разгрузить сервис аутентификации от лишних запросов, для чего и предназначен JWT, то в этой ситуации лучше сразу используйте PASETO — он и безопаснее и проще в использовании.


                                                              Вот забавный слайд из классной презентации PASETO с критикой JWT:

                                                                0

                                                                Не переусложненный jwt — это hmac =) которым еще, как тут сказали, "на заре веба" пользовались.

                                                                  0

                                                                  Не только. Иногда нужно шифровать. Плюс нужно определить формат сериализации как объекта так и подписи. Если мы ориентируемся на HMAC — всё это надо кустарно каждый раз изобретать самим, временами делая при этом детские ошибки. PASETO хорошо выдерживает баланс между недостаточно функциональным для этой задачи HMAC и избыточно гибким и проблемным JWT, давая разработчикам ровно те две фичи, которые им реально нужны: асимметрично подписанную структуру и симметрично зашифрованную структуру, с простым и очевидным API.

                                                                    0
                                                                    Почитал про это поделие — не впечатлило:
                                                                    — прибитие гвоздями алгоритма шифрования к версии а еще и к типу так себе решение;
                                                                    — local, public — это обязательно полностью писать? кобол все- равно не переплюнуть;
                                                                    — кодирование размера как 8 байт — автор думает что в токене будут терабайты? кодировки utf или asn смотрят с удивлением на этот финт;
                                                                    — jwt это не только токен, это еще oauth стандарт, рассказывающий про инфраструктуру.
                                                                      +1
                                                                      • На мой взгляд, в случае конкретно криптографии — прибить гвоздями решение правильное. Вариант когда этого не делают уже проверили в боевых условиях на примере JWT, и стало понятно, что это приводит к уязвимостям.
                                                                      • local/public — не уловил претензии. Многабукофф?
                                                                      • Размер 8 байт объяснён в доке, это защита от целочисленного переполнения: Due to the length being expressed as an unsigned 64-bit integer, it remains infeasible to generate/transmit enough data to create an integer overflow. Но, опять же, а Вам не пофигу, 4 байта там или 8? А делать переменную длину — усложнять алгоритм (который должен быть реализован на множестве разных языков) и создавать потенциальные баги/уязвимости, эта сложность не стоит экономии в 3 байта.
                                                                      • Вам никто не мешает положить в PASETO тот же набор "стандартных" ключей, к которому привыкли в OAuth. Напр. библиотека для Go предоставляет из коробки структуру с нужными полями, хотя к реализации PASETO она отношения не имеет и никто не заставляет именно такую структуру передавать внутри токена.

                                                                      В общем, если Вам критично сделать токен минимально-возможного размера, и экономия даже в 3 байта действительно важна — то PASETO не ставил перед собой таких задач, как впрочем и JWT, и Вам нужно кастомное решение. Но, возможно, если помимо размера нужна ещё и безопасность, то имеет смысл своё сжатие применять уже поверх стандартного токена PASETO/JWT.

                                                              Only users with full accounts can post comments. Log in, please.