О чем эта статья: мы разберемся, что такое 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 лучше учетных данных:
Учетные данные пользователя, как правило хранятся долго (месяцы). Как бы хорошо не был зашифрован запрос, при достаточном количестве времени его можно расшифровать. Если запрос, содержащий учетные данные перехвачен злоумышленником, у него будет много времени на расшифровку. Токены доступа имеют ограниченный срок годности (обычно ~15 минут). Этого времени не достаточно, чтобы расшифровать надежный шифр. К тому времени, когда зловредный алогритм расшифрует запрос, токен уже выйдет из обращения и будет бесполезен.
Использовать учетные данные, это медленно. Для валидации учетных данных сервер должен запросить их сохраненную копию из БД и сравнить с данными, которые пришли в запросе. Обращение к БД — дорогостоящая процедура, она сильно увеличивает время обработки запроса. Токены, с другой стороны, не требуют обращения к БД для валидации. Это позволяет снизить нагрузку на БД и ускорить обработку запросов сервером.
Время жизни токенов. Каждый токен имеет определенный срок годности. Эта информация зашита в его теле. При валидации, сервер извлекает данные из токена и проверяет, не истек ли срок.
Как хранить токены. Существует мнение, что некоторые виды токенов нужно хранить в БД вместе с остальными данными, это не так. 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](https://habrastorage.org/getpro/habr/upload_files/3da/dea/593/3dadea593579174c6a05b4395df33e2a.png)
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](https://habrastorage.org/getpro/habr/upload_files/3ce/8bc/2ac/3ce8bc2ac9e8679fade64393cef5ce39.png)
Что тут происходит:
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](https://habrastorage.org/getpro/habr/upload_files/240/316/d72/240316d72438209815dee26dca6d8c4e.png)
Что тут происходит:
ввод учетных данных, получение новой пары токенов
запрос данных с access токеном
проверка, не внесен ли токен в black-list
валидация access токена, передача данных на клиент
Обновление JWT
![Обновление JWT Обновление JWT](https://habrastorage.org/getpro/habr/upload_files/046/19a/f24/04619af24e26782dae81c466ae06fab6.png)
Что тут происходит:
запрос данных с access токеном
валидация access токена не прошла, возврат ошибки #401
запрос обновления токенов с refresh токеном,
проверка, не внесен ли токен в black-list
получение версии учетных данных из БД
валидация токена, проверка версии токена
генерация новой пары токенов, отправка на клиент
Неуспешное обновление JWT
![Неуспешное обновление JWT Неуспешное обновление JWT](https://habrastorage.org/getpro/habr/upload_files/799/93c/d33/79993cd33168ed2aec08a3b2775aab27.png)
Что тут происходит:
запрос обновления токенов
проверка, не внесен ли токен в black-list
получение версии учетных данных из БД
неуспешная валидация токена, редирект на страницу ввода учетных данных
В заключение
Вот, пожалуй, и все, что я хотел рассказать о JWT. Надеюсь, вышло не слишком нудно. Если появятся вопросы, пишите, буду рад любым комментариям.
P.S. Эта статья, на самом деле, часть исследования, посвященного методам авторизации. Основной материал я опубликовал в статье "Auth сервис без библиотек". Возможно, он будет интересен в контексте изучения JWT.
Еще по теме:
Комментарии (2)
lear
10.09.2024 08:41+2От JWT нет пользы в большинстве проектов.
Он нужен только для оптимизации запроса данных из других сервисов, только тогда выигрыш производительности нивелирует проблемы безопасности.
Если вы в JWT храните недостаточно данных (id пользователя и, допустим, роль) и затем запрашиваете все остальные данные в бд, то совокупного выигрыша нет.
malinichevvv
Вот кажется, элементарная тема, но столько нюансов... И я более чем уверен что JWT использует не более 15% сервисов