Ранее мы разобрались с основными концепциями аутентификации, поняли, что такое токены и их преимущества, а также настроили интеграцию токенов и JWT (JSON Web Tokens) в проект Django. Вы уже знаете, как установить нужные библиотеки и выполнить минимальную конфигурацию проекта для работы с JWT. Давайте перейдём к практике. И вот что мы будем делать:
- Создавать JWT для пользователей при их входе в систему.
- Проверять JWT в каждом запросе при обращении к API.
- Реализовывать логику входа (login) и выхода (logout) на основе JWT.
- Обеспечивать безопасность API и защиту данных.
Быстрое напоминание: что такое JWT?
JWT (JSON Web Token) — это компактный и безопасный способ передачи информации между сторонами, который используется для аутентификации и обмена данными.
Пример JWT состоит из трёх частей, разделённых точками:
header.payload.signature
- Header: заголовок, который содержит тип токена и алгоритм шифрования.
- Payload: полезные данные, например, информация о пользователе (id, роли и другие данные).
- Signature: подпись, которая генерируется с использованием секрета и обеспечивает защиту данных от изменений.
Подробнее об устройстве JWT можно почитать здесь.
Создание и выдача JWT
Что мы будем делать? При успешном входе пользователя мы будем генерировать JWT, который клиент сможет использовать для обращения к вашим API.
Установка библиотеки djangorestframework-simplejwt
Для работы с JWT нам потребуется сторонняя библиотека, которая упрощает создание и обработку токенов:
pip install djangorestframework-simplejwt
В settings.py добавьте настройки для SimpleJWT:
from datetime import timedelta
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'SIGNING_KEY': 'ваш_секретный_ключ',
}
используйте безопасный секретный ключ и не сохраняйте его в коде. Лучше вынести его в переменные окружения с использованием, например, библиотеки python-decouple.
Эндпоинты для входа и получения токена
Теперь нам необходимо определить два эндпоинта:
- /api/token/ — для получения токена при логине.
- /api/token/refresh/ — для обновления токена.
Для этого добавим маршруты в urls.py вашего приложения:
from django.urls import path
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Теперь, если мы отправим POST-запрос на /api/token/, указав правильные username и password, мы получим access- и refresh-токены. Вот пример запроса:
POST /api/token/
Content-Type: application/json
{
"username": "demo_user",
"password": "12345678"
}
Пример ответа:
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Использование JWT для защиты эндпоинтов
Теперь, когда мы умеем генерировать токены, давайте настроим ваши API так, чтобы они проверяли JWT в каждом запросе.
Давайте приведём пример защищённого представления. Создадим API, доступ к которому будет только у авторизованных пользователей:
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
class ProtectedAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
return Response({"message": "Привет, авторизованный пользователь!"})
Добавим маршрут для этого API в urls.py:
from django.urls import path
from .views import ProtectedAPIView
urlpatterns = [
path('api/protected/', ProtectedAPIView.as_view(), name='protected_api'),
]
Теперь, если мы попробуем сделать запрос к /api/protected/ без токена, сервер вернёт ошибку 401 Unauthorized:
{
"detail": "Учетные данные не были предоставлены."
}
Чтобы получить доступ, необходимо передать JWT в заголовок запроса:
Authorization: Bearer <ваш_access_токен>
И сервер ответит:
{
"message": "Привет, авторизованный пользователь!"
}
Выход (Logout)
JWT по своей природе не предоставляют полноценной возможности "разлогина" пользователя, так как токен остаётся действительным до истечения его срока действия. Однако мы можем использовать механизм черного списка для отзыва токенов.
Добавим возможность работы с черным списком:
pip install djangorestframework-simplejwt[blacklist]
В settings.py подключим приложение rest_framework_simplejwt.token_blacklist:
INSTALLED_APPS += [
'rest_framework_simplejwt.token_blacklist',
]
Далее необходимо выполнить миграции:
python manage.py migrate
Теперь мы можем создать эндпоинт для отзыва токенов:
from rest_framework_simplejwt.tokens import OutstandingToken, BlacklistedToken
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
class LogoutView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
try:
# Отзываем все токены пользователя
for token in OutstandingToken.objects.filter(user=request.user):
BlacklistedToken.objects.create(token=token)
return Response({"message": "Вы успешно вышли из системы"}, status=200)
except Exception as e:
return Response({"error": str(e)}, status=400)
Маршрут для выхода:
path('api/logout/', LogoutView.as_view(), name='logout'),
Теперь, вызвав /api/logout/ с токеном в заголовке, все токены этого пользователя будут аннулированы.
Дополнительные возможности: кастомизация токенов
Вы можете добавить пользовательские данные в payload токена. Для этого переопределите TokenObtainPairSerializer:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
# Добавляем пользовательские поля
token['email'] = user.email
token['is_staff'] = user.is_staff
return token
И укажите новый сериализатор в вашем представлении:
from rest_framework_simplejwt.views import TokenObtainPairView
class CustomTokenObtainPairView(TokenObtainPairView):
serializer_class = CustomTokenObtainPairSerializer
Не забудьте обновить маршрут:
path('api/token/', CustomTokenObtainPairView.as_view(), name='custom_token_obtain_pair'),
Теперь в выдаваемых токенах будут дополнительные данные.
Итог
Мы настроили JWT для аутентификации пользователей, научились генерировать токены и проверять запросы на их подлинность. Добавили эндпоинты для входа, выхода и защиты API. Это мощный инструмент, который часто используется в современных веб-приложениях, особенно при интеграции с мобильными приложениями или сторонними сервисами.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ