DRF JWT против DRF SimpleJWT: Какую библиотеку выбрать для аутентификации в Django?

Аутентификация является краеугольным камнем любого веб-приложения, обеспечивающего безопасность и персонализацию пользовательского опыта. В мире Django REST Framework (DRF), аутентификация на основе токенов, особенно с использованием JSON Web Tokens (JWT), стала де-факто стандартом благодаря своей stateless природе и масштабируемости. Однако, при выборе конкретной реализации JWT в DRF, разработчики часто сталкиваются с дилеммой: использовать более старую, но проверенную библиотеку djangorestframework-jwt или более современную и активно поддерживаемую djangorestframework-simplejwt.

Эта статья призвана помочь вам разобраться в особенностях обеих библиотек, сравнить их ключевые аспекты и сделать осознанный выбор, соответствующий потребностям вашего проекта.

Введение в JWT аутентификацию в Django REST Framework

Обзор аутентификации на основе токенов (JWT)

JSON Web Token (JWT) — это компактный и безопасный стандарт для передачи информации между сторонами в виде объекта JSON. Информация может быть проверена и доверена, потому что она подписана цифровой подписью. В контексте аутентификации, JWT обычно содержит информацию о пользователе (claim), которая используется сервером для идентификации и авторизации запросов без необходимости обращаться к базе данных при каждом запросе.

Процесс аутентификации с помощью JWT обычно выглядит так:

Клиент отправляет учетные данные (логин/пароль) на сервер.

Сервер проверяет учетные данные и, если они верны, генерирует JWT.

Сервер отправляет JWT клиенту.

Клиент сохраняет JWT (например, в локальном хранилище или cookie).

При последующих запросах к защищенным ресурсам, клиент включает JWT в заголовок Authorization (обычно в формате Bearer <token>).

Сервер проверяет подпись JWT и, если она действительна, извлекает информацию о пользователе для выполнения запроса.

Преимущества использования JWT в DRF

Использование JWT в DRF предоставляет ряд значительных преимуществ:

Stateless (без сохранения состояния): Серверу не нужно хранить информацию о сессии пользователя. Вся необходимая информация содержится в токене. Это упрощает масштабирование приложения, так как запросы могут обрабатываться любым сервером в кластере.

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

Мобильные приложения и SPA: JWT идеально подходит для взаимодействия с мобильными клиентами и одностраничными приложениями (SPA), поскольку он легко передается через HTTP заголовки.

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

Краткое описание DRF JWT и DRF SimpleJWT

DRF JWT (djangorestframework-jwt): Одна из первых и наиболее распространенных библиотек для реализации JWT в DRF. Она предоставляет базовый набор функций для генерации, обновления и проверки токенов. Исторически, эта библиотека была стандартом, но ее разработка замедлилась.

DRF SimpleJWT (djangorestframework-simplejwt): Более современная альтернатива, разработанная с учетом недостатков и опыта использования DRF JWT. Основное отличие заключается в использовании парных токенов (Access и Refresh токены) из коробки и более гибкой архитектуре.

DRF JWT: Подробный обзор

Архитектура и основные компоненты

DRF JWT оперирует в основном одним типом токена. Этот токен является как токеном доступа (используется для аутентификации запросов), так и, потенциально, содержит информацию для его обновления (если включена соответствующая функция). Основные компоненты:

Authentication Backend: Предоставляет класс аутентификации для DRF (JSONWebTokenAuthentication).

Views: Включает представления для получения токена (ObtainJSONWebToken), проверки токена (VerifyJSONWebToken) и обновления токена (RefreshJSONWebToken).

Utilities: Вспомогательные функции для создания и декодирования токенов.

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

Настройка и конфигурация DRF JWT

Установка выполняется через pip:

pip install djangorestframework-jwt

Настройка в settings.py включает добавление JSONWebTokenAuthentication в DEFAULT_AUTHENTICATION_CLASSES DRF и настройку словаря JWT_AUTH:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
        'rest_framework.authentication.SessionAuthentication', # Optional
        'rest_framework.authentication.BasicAuthentication',    # Optional
    ),
}

JWT_AUTH = {
    'JWT_SECRET_KEY': 'your-secret-key', # Замените на надежный ключ!
    'JWT_ALGORITHM': 'HS256',
    'JWT_VERIFY': True,
    'JWT_VERIFY_EXPIRATION': True,
    'JWT_LEEWAY': 0,
    'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=3600), # Токен действителен 1 час
    'JWT_ALLOW_REFRESH': True,
    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7), # Токен обновления действителен 7 дней
    'JWT_AUTH_HEADER_PREFIX': 'Bearer',
    'JWT_RESPONSE_PAYLOAD_HANDLER': \
        'path.to.your.custom_payload_handler', # Опционально: кастомный обработчик ответа
    'JWT_GET_USER_SECRET_KEY': None, # Опционально: per-user secret keys
    'JWT_DECODE_HANDLER': \
        'rest_framework_jwt.utils.jwt_decode_handler',
    'JWT_ENCODE_HANDLER': \
        'rest_framework_jwt.utils.jwt_encode_handler',
    'JWT_PAYLOAD_HANDLER': \
        'rest_framework_jwt.utils.jwt_payload_handler',
    'JWT_PAYLOAD_GET_USERNAME_HANDLER': \
        'rest_framework_jwt.utils.jwt_get_username_from_payload_handler',
}

# Не забудьте импортировать datetime
import datetime

Также необходимо добавить URL-адреса для получения и обновления токенов:

# urls.py

from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token, refresh_jwt_token, verify_jwt_token

urlpatterns = [
    path('auth/token/', obtain_jwt_token),      # Получение токена по логину/паролю
    path('auth/token/refresh/', refresh_jwt_token), # Обновление токена
    path('auth/token/verify/', verify_jwt_token),   # Проверка токена
    # ... другие URL-адреса вашего приложения
]

Примеры использования: Аутентификация, авторизация, обновление токенов

Получение токена: Клиент отправляет POST-запрос на /auth/token/ с телом {'username': '...', 'password': '...'}. В ответ получает JSON с ключом token.

Авторизация запросов: Клиент включает полученный токен в заголовок Authorization: Bearer <token>. DRF JWT аутентификатор проверяет токен и прикрепляет объект User к запросу (request.user).

Обновление токена: Когда срок действия токена истекает, клиент отправляет POST-запрос на /auth/token/refresh/ с телом {'token': 'старый_токен'}. В ответ получает новый токен, если старый был действителен и еще не вышел срок действия обновления.

Кастомизация DRF JWT: Расширение функциональности

DRF JWT предоставляет несколько точек для кастомизации, в основном через функции-обработчики (handlers), которые можно указать в JWT_AUTH settings:

JWT_PAYLOAD_HANDLER: Изменение данных, включаемых в payload токена (например, добавление дополнительных полей пользователя).

JWT_RESPONSE_PAYLOAD_HANDLER: Изменение формата ответа при успешном получении токена (например, возврат информации о пользователе вместе с токеном).

JWT_GET_USER_SECRET_KEY: Использование уникального ключа для каждого пользователя при подписи токена.

Пример кастомного JWT_RESPONSE_PAYLOAD_HANDLER:

# path/to/your/custom_payload_handler.py

from rest_framework_jwt.settings import api_settings
from django.contrib.auth.models import User

def custom_jwt_response_payload_handler(token: str, user: User | None = None, request = None) -> dict:
    
    """
    Кастомный обработчик ответа при получении токена.

    Включает токен и информацию о пользователе в ответ.

    Args:
        token: Сгенерированный JWT токен.
        user: Аутентифицированный пользователь.
        request: Объект запроса.

    Returns:
        Словарь с данными ответа.
    """
    
    # Получаем стандартный payload токена
    payload = api_settings.JWT_PAYLOAD_HANDLER(user)
    
    response_data = {
        'token': token,
        'user': {
            'id': user.id,
            'username': user.username,
            'email': user.email,
            # Добавьте другие поля по необходимости
        }
    }
    return response_data

Затем укажите путь к этой функции в settings.py:

# settings.py

JWT_AUTH = {
    # ... другие настройки
    'JWT_RESPONSE_PAYLOAD_HANDLER': 'path.to.your.custom_payload_handler.custom_jwt_response_payload_handler',
}

DRF SimpleJWT: Подробный обзор

Архитектура и основные компоненты

DRF SimpleJWT основан на концепции парных токенов: Access Token и Refresh Token.

Access Token: Краткоживущий токен, используемый для аутентификации каждого запроса к защищенным ресурсам. Его короткий срок жизни снижает риски, связанные с его компрометацией.

Refresh Token: Долгоживущий токен, используемый только для получения новой пары Access и Refresh токенов после истечения срока действия Access токена. Refresh токены обычно хранятся более безопасно и проверяются только на отдельном эндпоинте обновления.

Эта архитектура более устойчива к атакам (например, перехвату токена), так как компрометация Access токена дает злоумышленнику доступ лишь на ограниченное время. Компоненты SimpleJWT:

Реклама

Authentication Backend: rest_framework_simplejwt.authentication.JWTAuthentication.

Views: Предоставляет представления для получения парных токенов (TokenObtainPairView), обновления токенов (TokenRefreshView), проверки токенов (TokenVerifyView), а также получения токенов по утверждениям (claims) (TokenObtainPairView, TokenObtainPairView, и другие).

Tokens: Классы токенов (AccessToken, RefreshToken) для их создания и управления.

Настройка и конфигурация DRF SimpleJWT

Установка:

pip install djangorestframework-simplejwt

Настройка в settings.py. Добавление аутентификационного класса и настройка SIMPLE_JWT словаря:

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.SessionAuthentication', # Optional
        'rest_framework.authentication.BasicAuthentication',    # Optional
    ),
}

from datetime import timedelta

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),      # Access токен действителен 5 минут
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),       # Refresh токен действителен 7 дней
    'ROTATE_REFRESH_TOKENS': False,                   # Обновлять Refresh токен при обновлении Access токена?
    'BLACKLIST_AFTER_ROTATION': False,                # Помещать старый Refresh токен в блэклист?
    'UPDATE_LAST_LOGIN': False,                      # Обновлять поле last_login при аутентификации?

    'ALGORITHM': 'HS256',
    'SIGNING_KEY': 'your-secret-key',                # Замените на надежный ключ!
    'VERIFYING_KEY': None,
    'AUDIENCE': None,
    'ISSUER': None,
    'JWK_URL': None,
    'LEEWAY': 0,

    'AUTH_HEADER_TYPES': ('Bearer',),
    'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
    'USER_ID_FIELD': 'id',
    'USER_ID_CLAIM': 'user_id',
    'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',

    'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
    'TOKEN_TYPE_CLAIM': 'token_type',
    'TOKEN_USER_CLASS': 'django.contrib.auth.models.User',

    'JTI_CLAIM': 'jti',

    'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),     # Для Sliding токенов
    'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # Для Sliding токенов
}

Добавление URL-адресов:

# urls.py

from django.urls import path
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
)

urlpatterns = [
    path('auth/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), # Получение парных токенов
    path('auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), # Обновление токенов
    path('auth/token/verify/', TokenVerifyView.as_view(), name='token_verify'),   # Проверка токена доступа
    # ... другие URL-адреса вашего приложения
]

Примеры использования: Аутентификация, авторизация, обновление токенов

Получение токенов: Клиент отправляет POST-запрос на /auth/token/ с телом {'username': '...', 'password': '...'}. В ответ получает JSON с ключами access (Access Token) и refresh (Refresh Token).

Авторизация запросов: Клиент использует Access Token в заголовке Authorization: Bearer <access_token>. SimpleJWT аутентификатор проверяет Access Token и прикрепляет объект User к запросу.

Обновление токенов: Когда Access Token истекает (клиент получает 401 Forbidden), клиент отправляет POST-запрос на /auth/token/refresh/ с телом {'refresh': 'refresh_token'}. В ответ получает новую пару Access и Refresh токенов, если Refresh Token действителен.

Кастомизация DRF SimpleJWT: Расширение функциональности

SimpleJWT более гибок для кастомизации за счет использования классов и сериализаторов:

Кастомные сериализаторы токенов: Можно наследоваться от TokenObtainPairSerializer, TokenRefreshSerializer и других, чтобы изменить логику проверки учетных данных или добавить дополнительные данные в ответ.

Кастомные классы токенов: Можно наследоваться от AccessToken и RefreshToken для добавления кастомных claims (полей) в payload токена.

Расширение представлений: Наследование от стандартных представлений SimpleJWT.

Пример кастомного сериализатора для добавления полей пользователя в ответ при получении токенов:

# path/to/your/serializers.py

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from django.contrib.auth import get_user_model
from rest_framework import serializers

User = get_user_model()

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    
    @classmethod
    def get_token(cls, user: User):
        
        """
        Расширение payload Access и Refresh токенов.
        """
        
        token = super().get_token(user)

        # Добавьте кастомные claims в токен payload
        # token['username'] = user.username # Обычно не рекомендуется добавлять много данных в Access токен из-за его короткого срока жизни
        # token['email'] = user.email
        
        return token

    def validate(self, attrs: dict) -> dict:
        
        """
        Расширение ответа при успешном получении токенов.
        """
        
        data = super().validate(attrs)

        # Добавьте информацию о пользователе в ответ
        data['user'] = {
            'id': self.user.id,
            'username': self.user.username,
            'email': self.user.email,
            # Добавьте другие поля по необходимости
        }

        return data

Использование кастомного сериализатора в URLconf:

# urls.py

from django.urls import path
from rest_framework_simplejwt.views import TokenRefreshView, TokenVerifyView
from path.to.your.serializers import CustomTokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView

urlpatterns = [
    # Используем кастомный сериализатор
    path('auth/token/', TokenObtainPairView.as_view(serializer_class=CustomTokenObtainPairSerializer), name='token_obtain_pair'), 
    path('auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 
    path('auth/token/verify/', TokenVerifyView.as_view(), name='token_verify'),   
    # ... другие URL-адреса вашего приложения
]

DRF JWT против DRF SimpleJWT: Сравнительный анализ

Функциональность и особенности: Ключевые различия

| Аспект | DRF JWT | DRF SimpleJWT |
| :——————— | :————————————— | :—————————————— |
| Архитектура токенов | Один тип токена (Access/Implicit Refresh) | Парные токены (Access + Refresh) |
| Обновление токена | Требует отправки старого Access токена | Требует отправки Refresh токена |
| Blacklisting | Отсутствует из коробки | Встроенная поддержка blacklisting Refresh токенов (требует настройки) |
| Sliding Токены | Поддерживается | Поддерживается |
| Поддержка Python/Django | Поддержка новых версий может быть ограничена/отсутствовать | Активная поддержка последних версий Python и Django |
| Активность разработки| Низкая | Высокая |

Ключевое отличие – это модель парных токенов в SimpleJWT. Она считается более безопасной практикой, чем использование одного токена с функцией обновления, как в DRF JWT.

Безопасность: Сравнение подходов к защите токенов

DRF JWT:

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

Отсутствие встроенного блэклиста означает, что отозвать выданный токен (например, при выходе пользователя или смене пароля) сложнее. Приходится полагаться на истечение срока действия токена.

DRF SimpleJWT:

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

Встроенная поддержка блэклиста Refresh токенов (требует настройки rest_framework_simplejwt.token_blacklist) позволяет мгновенно отзывать скомпрометированные или устаревшие токены при выходе пользователя или смене пароля, значительно повышая безопасность.

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

Производительность: Влияние на скорость работы приложения

Обе библиотеки используют стандартные механизмы JWT, основанные на криптографической подписи. Проверка подписи токена выполняется быстро и не требует сетевых запросов или обращений к базе данных (если не используется blacklisting). Существенной разницы в производительности аутентификации одного запроса между ними нет.

Однако, при использовании blacklisting в SimpleJWT, каждый Refresh токен проверяется на наличие в блэклисте при запросе на обновление. Это добавляет одно обращение к базе данных (или к кешу, если настроено) при каждом обновлении токена. Это незначительное влияние на общую производительность, поскольку обновления происходят реже, чем обычные аутентифицированные запросы.

Простота использования и настройки: Что проще для начинающих?

Для базовой настройки аутентификации с одним токеном, DRF JWT может показаться немного проще, так как концепция одного токена интуитивно понятна.

Однако, если требуется реализовать более надежную схему с обновлением токенов и отзывом (что практически всегда нужно в реальных приложениях), SimpleJWT становится значительно проще.

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


Добавить комментарий