Недавно в одном из проектов было необходимо обеспечить пользователю смену пароля, чтобы при этом происходил выход со всех остальных устройств. Т.к. аутентификация была сделана на JWT токенах, то проблемой стало то, что невозможно вручную истечь токен после создания, он не имеет состояния и храниться на стороне клиента. В этой статье мы разберем генерацию JWT токена с возможностью занесения его в черный список на примере пустого проекта, а также протестируем, полученный результат с помощью Postman.
Немного о JWT
JSON Web Token (JWT) — это JSON объект, который определен в открытом стандарте RFC 7519. Он считается одним из безопасных способов передачи информации между двумя участниками. Основной его особенностью является, то что все необходимые аутентификационные данные хранятся в самом токене. Он состоит из 3-х основных частей: заголовок (header), нагрузка (payload) и подписи (signature).
Header – это JSON объект, который содержит в себе информацию о типе токена и способе шифрования: Payload – это полезная нагрузка токена, обычно там хранится идентификатор пользователя, время жизни токена или любая другая информация, на усмотрение издателя. Однако существуют зарезервированные названия полей, назначение, которых менять не рекомендуется:
iss: строка с уникальным идентификатором стороны, генерирующей токен.
sub: строка, которая является уникальным идентификатором стороны, о которой содержится информация в данном токене (subject).
aud: массив чувствительных к регистру строк или URI, являющийся списком получателей данного токена.
exp: время в формате Unix Time, определяющее момент, когда токен станет невалидным (expiration).
nbf: в противоположность ключу exp, это время в формате Unix Time, определяющее момент, когда токен станет валидным (not before).
jti: строка, определяющая уникальный идентификатор данного токена (JWT ID).
iat: время в формате Unix Time, определяющее момент, когда токен был создан.
Signature – подпись, которая формируется следующим образом:
1. Header и Payload приводятся к формату base64. 2. Далее они соединяются в одну строку через точку.
3. По алгоритму, указанному в header, полученная строка хешируется на основе секретного ключа.
Результатом работы данного алгоритма и является подпись. Чтобы получить сам JWT необходимо соединить через точку header, payload и signature.
Аутентификация при помощи JWT
Обычно пользователь получает JWT при регистрации или первом логине. Он сохраняет его у себя на устройстве и при последующих обращения к API передает этот токен со всеми запросами. Как правило токен кладется в заголовок запроса. Получив токен, приложение сперва проверяет его подпись. Убедившись, что подпись действительна, приложение извлекает из части полезной нагрузки сведения о пользователе и на их основе авторизует его.
Время жизни токена
Очень важным вопросом при использовании JWT является время жизни токена. На этот вопрос нет универсального ответа, все зависит от сервиса. Однако нужно учитывать 2 момента: 1. Если время жизни токена будет слишком большим, это может привести к проблемам безопасности. Например, если злоумышленнику удалось скомпрометировать токен пользователя, он может использовать его до тех пор, пока не истечет его время жизни. 2. Малое время жизни токена может привести к излишней нагрузке на сервер, так как пользователю придется постоянно рефрешить старый токен (запрашивать новый)
Отсюда вытекает необходимость дать пользователю возможность самому сбросить все свои токены. Например, в случае компрометации токена злоумышленником, для смены пароля или выхода со всех устройств. Существует несколько способов отозвать существующие токены, например выписывать токены на основе уникального идентификатора пользователя или создать черный список для выписанных токенов.
Разберем на примере Django c использование django rest framework и библиотеки Simple JWT как заносить токены в черный список. Сразу стоит отметит, что библиотеке Simple JWT сразу предоставляет нам удобное приложения "Черного списка", которое мы и будем использовать.
Первоначальная настройка проекта
Создадим пустой проект командой django-admin startproject jwt_auth_project. Сразу же создадим приложение для работы с пользователями командой python manage.py startapp users и зарегистрируем его в INSTALLED_APPS в файле settings.py:
Создадим виртуальное окружение, установим библиотеки djangorestframework и djangorestframework-simplejwt и пропишем:
В настройках REST_FRAMEWORK по умолчанию прописываем разрешения только для аутентифицированных пользователей и в качестве бэкенда аутентификации указываем класс, который предоставляет нам библиотека simplejwt.
Настройки для simplejwt прописываются также в файле settings.py. В данной статье мы не будем подробно останавливаться на каждой из них, т.к. все они подробно описаны в документации. Отметим, что время жизни токена мы выбрали 5 минут, а время жизни рефреш токена 2 дня.
После этого необходимо обновить INSTALLED_APPS:
В приложении users создадим файл urls.py и в файле jwt_auth_project/urls.py зарегистрируем его:
Далее нам необходимо написать кастомный менеджер для будущей модели пользователя. В приложении users создадим файл managers.py и наберем следующий код:
Теперь мы можем создать собственную модель пользователя в файле users/models.py:
Далее необходимо указать Django какую модель пользователя необходимо использовать для аутентификации. Для этого в файле настроек пропишем следующую строчку:
Теперь можно запустить сервер командой python manage.py runserver создать и провести миграции командами python manage.py makemigrations и python manage.py migrate. После этого в нашей базе данных создадутся необходимые таблицы для дальнейшей работы.
Получение токенов, регистрация пользователя, информация о пользователе
На данном этапе у нас все готово для написания основных точек API. Создадим файл users/serializers.py и напишем туда основные сериализаторы:
После чего в файле users/views.py напишем вью для регистрации и отдачи информации о пользователе:
Теперь необходимо определить маршруты для наших представлений в файле users/urls.py:
Стоит отметить, что представления для получения токена и рефреша нам предоставляет библиотека rest_framework_simplejwt и нет необходимости писать их вручную.
Занесения существующих токенов в "Черный список"
Теперь пользователь может зарегистрироваться в нашем приложении и получить о себе информацию. Осталось дать возможность разлогиниться со всех устройств(внести токены в черный список). Библиотека simple_jwt предоставляет нам две модели OutstandingToken и BlacklistedToken. Их мы и будем использовать для занесения токенов в черный список.
Для этого напишем еще одно вью в файле users/views.py:
И регистрируем в users/urls.py:
Тестирование полученного API с помощью Postman
Теперь мы можем протестировать полученный API, для этого мы будем использовать Postman. Первое что нам нужно сделать это отправить следующий запрос:
Стоит отметить, что благодаря тому, что мы определили access_token и refresh_token, как динамические свойства в модель User и указали их в сериализаторе, то не нужно дополнительно запрашивать их после регистрации.
После регистрации пользователь может получить информацию о своем аккаунте, для этого в запрос необходимо добавить заголовок Authorization с значением Bearer {access_token}: