Обзор JWT: структура и принципы работы
JSON Web Token (JWT) – это компактный, самодостаточный способ безопасной передачи информации между сторонами в виде JSON-объекта. В контексте Django, JWT обычно используется для аутентификации пользователей. Токен состоит из трех частей, разделенных точками:
- Заголовок (Header): Содержит информацию о типе токена и алгоритме шифрования.
- Полезная нагрузка (Payload): Содержит утверждения (claims) о пользователе и другую мета-информацию, например,
user_id,expiration timeи другие кастомные данные. - Подпись (Signature): Создается путем шифрования заголовока и полезной нагрузки с использованием секретного ключа.
Когда пользователь успешно аутентифицируется, сервер генерирует JWT и отправляет его клиенту. Клиент затем включает этот токен в заголовки Authorization при каждом последующем запросе к серверу. Сервер, получив токен, проверяет его подпись, и, если она действительна, разрешает доступ к ресурсу.
Преимущества и недостатки использования JWT в Django проектах
Преимущества:
- Простота и масштабируемость: JWT не требует хранения состояния на сервере, что упрощает масштабирование.
- Кросс-доменность: JWT можно использовать для аутентификации в различных доменах и на разных платформах.
- Стандартизация: JWT – это общепринятый стандарт, поддерживаемый множеством библиотек и фреймворков.
Недостатки:
- Сложность отзыва: После выдачи JWT остается действительным до истечения срока его действия. Отзыв токена требует дополнительных механизмов.
- Размер токена: JWT может быть достаточно большим, особенно если содержит много информации в полезной нагрузке.
- Риск компрометации секретного ключа: Утечка секретного ключа делает все выпущенные токены уязвимыми.
Стандартные подходы к хранению JWT токенов на стороне клиента
Обычно JWT хранятся на стороне клиента в:
- LocalStorage: Просто, но подвержено XSS-атакам.
- Cookies: Более безопасно, особенно с атрибутами
HttpOnlyиSecure. - SessionStorage: Живет только пока открыта вкладка браузера.
Выбор места хранения зависит от требований к безопасности и удобству использования.
Проблема удаления JWT токена при выходе из системы
Почему нельзя просто удалить JWT токен из браузера?
Удаление JWT токена из браузера (например, удаление cookie или очистка localStorage) предотвращает его использование клиентом для будущих запросов. Однако, токен остается действительным до истечения срока его действия. Если злоумышленник получит этот токен (например, через XSS), он сможет использовать его для аутентификации, пока токен не истечет.
Уязвимости, связанные с отсутствием корректного удаления токена
Отсутствие корректного удаления или отзыва JWT токена может привести к:
- Несанкционированному доступу: Злоумышленник может использовать украденный токен для доступа к ресурсам, пока он действителен.
- Атакам воспроизведения: Если токен перехвачен, его можно использовать повторно.
- Компрометации данных: Получив доступ к аккаунту, злоумышленник может изменить или украсть личные данные.
Разница между отзывом токена (token revocation) и его удалением из браузера
Удаление токена из браузера – это клиентская операция, которая просто убирает токен из хранилища браузера. Это предотвращает автоматическую отправку токена с каждым запросом. Отзыв токена (token revocation) – это серверная операция, которая делает токен недействительным, даже если он все еще хранится в браузере. Отзыв требует, чтобы сервер отслеживал отозванные токены и отклонял их.
Стратегии корректного удаления JWT токена при выходе из системы
Использование blacklist (черного списка) для отзыва токенов
Blacklist – это список токенов, которые были отозваны. При каждом запросе сервер проверяет, находится ли токен в blacklist. Если токен есть в списке, запрос отклоняется, даже если подпись токена действительна.
Реализация blacklist в Django с использованием Redis или базы данных
Blacklist можно реализовать с использованием Redis (для скорости) или базы данных (для надежности). Пример с Redis:
import redis
from django.conf import settings
redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)
def add_token_to_blacklist(token: str, expiry: int):
"""Adds a token to the Redis blacklist with an expiry time."""
redis_client.setex(token, expiry, "blacklisted")
def is_token_blacklisted(token: str) -> bool:
"""Checks if a token is in the Redis blacklist."""
return redis_client.get(token) == b"blacklisted"
Сокращение времени жизни (expiration time) токена
Чем короче время жизни токена, тем меньше окно для злоумышленника, который мог его украсть. Однако, слишком короткое время жизни может привести к частому обновлению токена и ухудшению пользовательского опыта. Необходимо найти баланс.
Применение refresh tokens и их отзыв при выходе
Refresh token – это долгоживущий токен, который используется для получения новых access token (обычных JWT с коротким временем жизни). При выходе из системы можно отозвать refresh token, что сделает невозможным получение новых access token.
Практическая реализация удаления JWT токена в Django
Настройка Django REST Framework для работы с JWT
Для работы с JWT в Django REST Framework можно использовать библиотеку djangorestframework-simplejwt. Установите её:
pip install djangorestframework djangorestframework-simplejwt
Добавьте в settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
}
Реализация endpoint’а для выхода из системы (logout)
Создайте endpoint для выхода из системы, который будет добавлять access token в blacklist и отзывать refresh token (если он используется).
Добавление токена в blacklist при выходе
Используйте функцию add_token_to_blacklist (из примера выше) для добавления токена в blacklist при выходе.
Пример кода Django для обработки выхода пользователя и отзыва токена
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings
import redis
redis_client = redis.StrictRedis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=0)
def add_token_to_blacklist(token: str, expiry: int):
"""Adds a token to the Redis blacklist with an expiry time."""
redis_client.setex(token, expiry, "blacklisted")
class LogoutView(APIView):
def post(self, request):
try:
refresh_token = request.data["refresh_token"]
token = RefreshToken(refresh_token)
# Blacklist access token
access_token = str(token.access_token)
add_token_to_blacklist(access_token, settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'].total_seconds())
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
Добавьте URL в urls.py:
from django.urls import path
from .views import LogoutView
urlpatterns = [
path('logout/', LogoutView.as_view(), name='logout')
]
Безопасность и лучшие практики
Защита от CSRF атак при выходе из системы
Убедитесь, что ваш endpoint для выхода из системы защищен от CSRF-атак. Используйте CSRF-токены и проверяйте их на сервере.
Обработка ошибок и логирование при отзыве токенов
Обрабатывайте возможные ошибки при отзыве токенов (например, отсутствие refresh token) и логируйте их для отладки и мониторинга.
Рекомендации по безопасному хранению и передаче JWT токенов
- Используйте HTTPS для передачи токенов.
- Храните токены в Cookies с атрибутами
HttpOnlyиSecure(если это возможно). - Не храните конфиденциальную информацию в полезной нагрузке токена.
- Регулярно меняйте секретный ключ.