О чем эта статья: мы разберемся, что такое JSON Web Token, как он устроен и для чего используется, рассмотрим такие приемы, как «black-list токенов» и «контроль версий» токенов. Для наглядности, в конце будут блок-схемы клиент-серверных запросов с пояснениями.

Для кого эта статья: для тех, кто хочет детально понять что такое JWT, а так же для тех, кто просто ищет схему реализации.

Термины

  • Идентификация — процесс получения идентификатора пользователя: логин / e-mail /id

  • Аутентификация — подтверждение личности пользователя (с помощью пароля, отпечатка пальца, и т.п.)

  • Авторизация — предоставление прав пользователю, выдача токена

  • Валидация — процесс проверки «куска» информации на соответствие требованиям программы, или просто на совпадение с копией, хранимой в базе данных.

  • Токен — ключ аутентификации пользователя

  • Credentials — учетные данные пользователя: логин, пароль, google id, и т.п.

  • БД — база данных

  • Клиент — уровень представления данных (см клиент-серверная архитектура). Имеет графический интерфейс для взаимодействия с пользователем. пример: веб-сайт в интернете.

  • Сервер — уровень получения и обработки данных (см клиент-серверная архитектура). Не имеет графического интерфейса, принимает запросы от клиентов через API.

  • API — Application Program Interface, набор команд, позволяющий обратиться к приложению

  • Метод API — конкретная команда, позволяющая обратиться к приложению

  • Публичные методы API — те, которые доступны без аутентификации пользователя, например: главная страница сайта в интернете.

  • Защищенные методы API — требующие обязательной аутентификации пользователя, например: личный кабинет пользователя на сайте.

  • Эндпоинт — url адрес метода API в интернете

Что такое JWT

JWT (Json Web Token) — ключ аутентификации пользователя. Используется для запросов к защищенным методам API.

Для чего нужны JWT: чтобы не передавать учетные данные пользователя с каждым запросом к серверу.

Чем JWT лучше учетных данных:

  1. Учетные данные пользователя, как правило хранятся долго (месяцы). Как бы хорошо не был зашифрован запрос, при достаточном количестве времени его можно расшифровать. Если запрос, содержащий учетные данные перехвачен злоумышленником, у него будет много времени на расшифровку. Токены доступа имеют ограниченный срок годности (обычно ~15 минут). Этого времени не достаточно, чтобы расшифровать надежный шифр. К тому времени, когда зловредный алогритм расшифрует запрос, токен уже выйдет из обращения и будет бесполезен.

  2. Использовать учетные данные, это медленно. Для валидации учетных данных сервер должен запросить их сохраненную копию из БД и сравнить с данными, которые пришли в запросе. Обращение к БД — дорогостоящая процедура, она сильно увеличивает время обработки запроса. Токены, с другой стороны, не требуют обращения к БД для валидации. Это позволяет снизить нагрузку на БД и ускорить обработку запросов сервером.

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

Как хранить токены. Существует мнение, что некоторые виды токенов нужно хранить в БД вместе с остальными данными, это не так. JWT были придуманы специально для того, чтобы не хранить их и не сверять с БД при каждом запросе. Валидация токенов происходит прямо на сервере. Как это реализовано, я расскажу ниже.

Оговорюсь: бывают ситуации, когда нам нужно отозвать токен до истечения его срока годности и добавить его в «черный список». Для хранения этого списка используют
In-Memory Cache или специальную базу данных — Redis (см раздел "Black-list токенов").

Как передавать токены

от сервера к клиенту:

  • в заголовке запроса «Authrorization» с добавлением слова Bearer

  • либо используя заголовок «Set-Cookie»:

Set-Cookie: accessToken=<jwt>; HttpOnly; Sequre; SameSite=Strict;
Set-Cookie: refreshToken=<refresh-token>; HttpOnly; Sequre; SameSite=Strict;

от клиента к серверу:

  • в заголовке запроса «Authrorization» с добавлением слова Bearer

Authorization: Bearer <jwt>
  • либо используя заголовок «Cookie»

Cookie: accessToken=<jwt>

Виды JWT

  • «access token» — проверяется при каждом обращении к защищенному API

    • многоразовый

    • присылается с каждым запросом к API в заголовке «authorization»

    • имеет короткий срок годности (обычно ~15 мин)

    • когда срок годности выходит, сервер возвращает #401

  • «refresh token» — токен для получения новой пары токенов (access и refresh)

    • одноразовый

    • имеет длительный срок годности (обычно несколько дней)

    • отправляется клиентом на эндпоинт ~/auth/refresh, когда истечет срок годности access токена и сервер вернет #401

  • «barer token» — частный случай access токена. В рамках веб приложений эти термины можно использовать, как синонимы.

Структура JWT

Токен состоит из 3 частей разделенных точкой:

Структура JWT
Структура JWT
  • header — содержит информацию об алгоритме шифрования и типе токена (JWT)

  • payload — данные токена. Стандартные поля:

    • iss (Issuer) — издатель токена. Как правило — uuid приложения, выпустившего токен.

    • sub (Subject) — собственник токена. Как правило — uuid пользователя

    • aud (Audience) — массив url серверов, для которых предназначен токен

    • exp (Expiration Time) — время, в течение которого токен считается валидным.

    • nbf (Not Before) — временная метка, до которй токен не считается валидным

    • iat (Issued At) — время создания токена

    • jti (JWT ID) — уникальный идентификатор токена

  • signature — строка, полученная из частей токена (header + payload) при помощи шифрования.

Валидация токенов

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

валидация JWT
валидация JWT

Что тут происходит:

1. Извлекаем JWT из заголовка запроса

2. определяем алгоритм шифрования токена. (параметр “header.alg”)

3. при помощи алгоритма, шифруем:
header + “.” + payload

4. сравниваем полученное значение с третьей частью токена (signature)
Значения совпали? — идем дальше. Нет? — возвращаем на клиент #401

5. проверяем срок годности токена. (“payload.exp”)
Срок не истек? — идем дальше.
Истек? — возвращаем #401

6. дополнительно можно проверить остальные параметры payload: iss, sub, aud, nbf

7. отдаем на клиент запрошенные данные

Black-list токенов

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

Токен — коротко живущая информация. Чтобы токены не накапливались в «черном списке» их можно периодически удалять, но проще — использовать специальную базу данных с поддержкой TTL (Time to Live). Такие БД (например Redis) позволяют назначить записи срок годности, после истечения которого данные будут удалены автоматически.

Вопрос: если мы используем БД с поддержкой TTL, зачем нам вообще «черный список»? Можно просто хранить все токены в БД, удалять отозванные, и проверять, есть ли такой токен при каждом запросе.

Ответ: конечно можно, но количество таких токенов будет существенно больше. Это увеличит объем потребляемой памяти, и замедлит запросы к БД. больше данных => медленнее поиск в БД.

Контроль версий

Разберем ситуацию:

  • Ваши учетные данные были украдены.

  • Злоумышленник входит в приложение от вашего имени и получает пару токенов. Когда срок жизни токенов истекает, он запрашивает новые в обмен на refresh token, и т. д.

  • Вы узнаете, что страница взломана и сбрасываете пароль. Но, вы не можете отозвать все старые токены потому, что у вас их нет, они нигде не хранятся.

Чтобы решить эту проблему используют «контроль версий учетных данных»

  • В таблицу нашей БД, где хранятся учетные данные, добавляем поле «version»

  • При создании refresh токена добавляем поле «version» в payload токена.

  • При каждой проверке refresh токена сверяем номер версии с номером из БД

  • Если номер версии не совпал, возвращаем #401

Вопрос: а чем это лучше, чем хранить сам refresh токен в базе данных?

Ответ 1: Утечка данных из БД (такое бывает) никогда не приведет к утечке токенов, а украденный хеш пароля ничего не даст злоумышленнику, потому что его еще нужно дешифровать.

Ответ 2: Если пользователь входите в приложение с разных устройств, сервис авторизации выдаст токены и сохранит их в БД. Выдавать всем один refresh token небезопасно, => количество записей в БД = количеству токенов. Больше записей => медленнее обработка запроса

Ответ 3: В случае сброса пароля серверу придется удалить из базы все токены, привязанные к пользователю. Удаление данных из таблицы — трудоемкий процесс, он требует переиндексации всей таблицы => возрастает нагрузка на БД => медленнее обработка запроса.

Использование JWT

использование JWT
использование JWT

Что тут происходит:

  1. ввод учетных данных, получение новой пары токенов

  2. запрос данных с access токеном

  3. проверка, не внесен ли токен в black-list

  4. валидация access токена, передача данных на клиент

Обновление JWT

Обновление JWT
Обновление JWT

Что тут происходит:

  1. запрос данных с access токеном

  2. валидация access токена не прошла, возврат ошибки #401

  3. запрос обновления токенов с refresh токеном,

  4. проверка, не внесен ли токен в black-list

  5. получение версии учетных данных из БД

  6. валидация токена, проверка версии токена

  7. генерация новой пары токенов, отправка на клиент

Неуспешное обновление JWT

Неуспешное обновление JWT
Неуспешное обновление JWT

Что тут происходит:

  1. запрос обновления токенов

  2. проверка, не внесен ли токен в black-list

  3. получение версии учетных данных из БД

  4. неуспешная валидация токена, редирект на страницу ввода учетных данных

В заключение

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

P.S. Эта статья, на самом деле, часть исследования, посвященного методам авторизации. Основной материал я опубликовал в статье "Auth сервис без библиотек". Возможно, он будет интересен в контексте изучения JWT.

Еще по теме:

Комментарии (2)


  1. malinichevvv
    10.09.2024 08:41

    Вот кажется, элементарная тема, но столько нюансов... И я более чем уверен что JWT использует не более 15% сервисов


  1. lear
    10.09.2024 08:41
    +2

    От JWT нет пользы в большинстве проектов.

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

    Если вы в JWT храните недостаточно данных (id пользователя и, допустим, роль) и затем запрашиваете все остальные данные в бд, то совокупного выигрыша нет.