Подробно про JWT
О чем эта статья: мы разберемся, что такое 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 токенов»).
Как передавать токены
от сервера к клиенту:
Set-Cookie: accessToken=; HttpOnly; Sequre; SameSite=Strict;
Set-Cookie: refreshToken=; HttpOnly; Sequre; SameSite=Strict;
от клиента к серверу:
Authorization: Bearer
Cookie: accessToken=
Виды JWT
«access token» — проверяется при каждом обращении к защищенному API
многоразовый
присылается с каждым запросом к API в заголовке «authorization»
имеет короткий срок годности (обычно ~15 мин)
когда срок годности выходит, сервер возвращает #401
«refresh token» — токен для получения новой пары токенов (access и refresh)
одноразовый
имеет длительный срок годности (обычно несколько дней)
отправляется клиентом на эндпоинт ~/auth/refresh, когда истечет срок годности access токена и сервер вернет #401
«barer token» — частный случай access токена. В рамках веб приложений эти термины можно использовать, как синонимы.
Структура JWT
Токен состоит из 3 частей разделенных точкой:
Структура 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
Что тут происходит:
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
Что тут происходит:
ввод учетных данных, получение новой пары токенов
запрос данных с access токеном
проверка, не внесен ли токен в black-list
валидация access токена, передача данных на клиент
Обновление JWT
Обновление JWT
Что тут происходит:
запрос данных с access токеном
валидация access токена не прошла, возврат ошибки #401
запрос обновления токенов с refresh токеном,
проверка, не внесен ли токен в black-list
получение версии учетных данных из БД
валидация токена, проверка версии токена
генерация новой пары токенов, отправка на клиент
Неуспешное обновление JWT
Неуспешное обновление JWT
Что тут происходит:
запрос обновления токенов
проверка, не внесен ли токен в black-list
получение версии учетных данных из БД
неуспешная валидация токена, редирект на страницу ввода учетных данных
В заключение
Вот, пожалуй, и все, что я хотел рассказать о JWT. Надеюсь, вышло не слишком нудно. Если появятся вопросы, пишите, буду рад любым комментариям.