🐛 Типичные ошибки — Урок 44

← К оглавлению урока

⚡ Топ-5 ошибок

  • Нет token_blacklist в INSTALLED_APPS → ImproperlyConfigured при BLACKLIST_AFTER_ROTATION=True
  • Нет миграций после добавления token_blacklist → OperationalError: no such table
  • secure=True на localhost → куки не устанавливаются без HTTPS
  • create() вместо create_user() → пароль в открытом виде, аутентификация не работает
  • Нет AllowAny на LoginView/RegisterView → 403 для всех неавторизованных запросов

Распространённые ошибки при работе с JWT в DRF

Ошибка 1: Отсутствие token_blacklist в INSTALLED_APPS

Симптом: ImproperlyConfigured: 'BLACKLIST_AFTER_ROTATION': True requires 'rest_framework_simplejwt.token_blacklist' in INSTALLED_APPS
# Неверно — token_blacklist не добавлен
INSTALLED_APPS = [
    'rest_framework_simplejwt',
    # ...
]

SIMPLE_JWT = {
    'BLACKLIST_AFTER_ROTATION': True,  # Требует token_blacklist!
}
# Верно
INSTALLED_APPS = [
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist',  # Добавить
    # ...
]

После добавления обязательно выполнить: python manage.py migrate

Ошибка 2: Забыть выполнить миграции

Симптом: OperationalError: no such table: token_blacklist_blacklistedtoken
# После добавления token_blacklist в INSTALLED_APPS
python manage.py migrate

# Проверить наличие таблиц
python manage.py showmigrations token_blacklist

Ошибка 3: secure=True на localhost

Симптом: куки не устанавливаются, в DevTools видно Set-Cookie без сохранения, браузер игнорирует куку.
# Неверно для разработки
response.set_cookie(
    key='access_token',
    value=str(access_token),
    secure=True,  # Требует HTTPS — не работает на http://localhost
)
# Верно для разработки
import os
IS_PROD = os.environ.get('DJANGO_ENV') == 'production'

response.set_cookie(
    key='access_token',
    value=str(access_token),
    secure=IS_PROD,     # False в dev, True в prod
    httponly=True,
    samesite='Lax',
)

Ошибка 4: create() вместо create_user() в сериализаторе

Симптом: пользователь создаётся, но аутентификация не работает. authenticate() возвращает None.
# Неверно — пароль сохраняется в открытом виде
def create(self, validated_data):
    user = User.objects.create(**validated_data)  # Пароль не хэшируется!
    return user
# Верно — create_user() хэширует пароль через set_password()
def create(self, validated_data):
    user = User.objects.create_user(
        username=validated_data['username'],
        password=validated_data['password'],
        email=validated_data.get('email', '')
    )
    return user

Django хранит пароли в формате {algorithm}${iterations}${salt}${hash}. При вызове create() напрямую пароль остаётся как есть, и authenticate() не сможет сравнить хэш.

Ошибка 5: Отсутствие AllowAny на LoginView и RegisterView

Симптом: 403 Forbidden при попытке залогиниться или зарегистрироваться — ещё до ввода данных.
# Неверно — если DEFAULT_PERMISSION_CLASSES = IsAuthenticated
class LoginView(APIView):
    # permission_classes не указан → наследует IsAuthenticated
    def post(self, request):
        ...
# Верно — явно разрешаем доступ анонимам
from rest_framework.permissions import AllowAny

class LoginView(APIView):
    permission_classes = [AllowAny]  # Обязательно!
    def post(self, request):
        ...

class RegisterView(APIView):
    permission_classes = [AllowAny]  # Обязательно!
    def post(self, request):
        ...

Ошибка 6: Хранение токенов в localStorage (небезопасно)

Риск безопасности: XSS-атака может прочитать localStorage и похитить токены.
// Неверно — небезопасное хранение
localStorage.setItem('access', response.data.access);
// Верно — httpOnly-куки (устанавливает сервер)
// На фронтенде ничего не нужно — браузер управляет куками автоматически
fetch('/api/data/', { credentials: 'include' });

Ошибка 7: Незащищённый LogoutView

Симптом: после логаута refresh-токен остаётся валидным до истечения срока (1 день).
# Неполный вариант из лекции — только удаляет куки
class LogoutView(APIView):
    def post(self, request):
        response = Response(status=status.HTTP_204_NO_CONTENT)
        response.delete_cookie('access_token')
        response.delete_cookie('refresh_token')
        return response
# Полный вариант — с инвалидацией refresh-токена
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework_simplejwt.exceptions import TokenError

class LogoutView(APIView):
    def post(self, request):
        refresh_token_str = request.COOKIES.get('refresh_token')
        if refresh_token_str:
            try:
                RefreshToken(refresh_token_str).blacklist()
            except TokenError:
                pass  # Уже истёк — нечего делать

        response = Response(status=status.HTTP_204_NO_CONTENT)
        response.delete_cookie('access_token')
        response.delete_cookie('refresh_token')
        return response

Ошибка 8: Неправильный порядок middleware

Симптом: токены не обрабатываются, хотя middleware подключён.
# Неверно — JWTAuthenticationMiddleware стоит ПЕРЕД SessionMiddleware
MIDDLEWARE = [
    'myapp.middleware.JWTAuthenticationMiddleware',  # Слишком рано!
    'django.contrib.sessions.middleware.SessionMiddleware',
    # ...
]
# Верно — JWTAuthenticationMiddleware после встроенных middleware
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'myapp.middleware.JWTAuthenticationMiddleware',  # В конце
]