Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. DRF: Аутентификация
drf_auth

DRF: Аутентификация

Session/Token/JWT, OAuth2/OIDC, permissions и throttling

DRF: аутентификация, permissions и throttling

Статус: актуально для Django 5.2/6.0 и современных версий Django REST Framework. JWT и OAuth/OIDC реализуются сторонними пакетами и требуют сверки их совместимости отдельно.

В защите API участвуют три разных механизма:

  • authentication устанавливает, кто обращается к API: request.user и request.auth;
  • permissions решают, разрешено ли этому субъекту действие;
  • throttling временно ограничивает частоту запросов.

Наличие валидного токена ещё не даёт права изменить любой объект. И наоборот, throttling не доказывает личность и не является полноценной защитой от DDoS или перебора пароля.

#Безопасный глобальный минимум

По умолчанию DRF использует AllowAny. Для приватного продукта безопаснее закрыть API глобально, а публичные endpoint открывать явно:

REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], }

AllowAny не «плохой» класс: он нужен health check, публичному каталогу или регистрации. Важно, чтобы открытость была заметным решением конкретной view.

Порядок authentication classes имеет значение. DRF перебирает их по очереди, а первый подходящий механизм устанавливает пользователя. Первый класс также влияет на то, получит неаутентифицированный клиент 401 с WWW-Authenticate или 403.

#Выбор механизма

МеханизмПрактическое применениеВажные ограничения
SessionAuthenticationБраузерное приложение на доверенном доменеCookie, сессия и CSRF-защита небезопасных методов
BasicAuthenticationДиагностика или закрытая интеграция по TLSУчётные данные передаются на каждом запросе; не типичный production login
TokenAuthenticationНебольшой API с простым серверным отзывомОдин сохраняемый токен на пользователя, штатно без срока действия
JWT через Simple JWTМобильные и распределённые клиентыСложнее отзыв, ротация и безопасное хранение
OAuth 2.0 / OpenID ConnectДелегированный доступ и внешний входЭто протокол и отдельная модель угроз, а не один класс DRF

Не выбирайте JWT только потому, что приложение «масштабируется». Обычная Django session хорошо работает с общим хранилищем сессий и проще отзывается. JWT уменьшает часть серверного состояния, но обычно всё равно требует загрузить пользователя и не отменяет permissions.

#Session authentication и CSRF

Для браузерного клиента на том же сайте session cookie — естественный выбор. Браузер прикладывает cookie автоматически, поэтому небезопасные методы POST, PUT, PATCH и DELETE должны проходить CSRF-защиту.

SessionAuthentication проверяет CSRF для аутентифицированных запросов. Это не повод строить login endpoint, который «сначала анонимен, значит CSRF не нужен»: вход нужно выполнять стандартной Django view или собственной view с явной CSRF-защитой.

Клиент обычно получает CSRF cookie, читает доступный токен и отправляет его в заголовке X-CSRFToken. SameSite помогает, но не заменяет CSRF-проверку. Настройки Secure, HttpOnly, домена cookie и доверенных origins зависят от реальной схемы доменов и HTTPS.

#Встроенный TokenAuthentication

Для токенов DRF нужно подключить приложение и миграции:

INSTALLED_APPS = [ # ... "rest_framework.authtoken", ] REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.TokenAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], }

После migrate клиент отправляет:

Authorization: Token 9944b09199c62bcf9418ad...

Этот механизм обязан использоваться только по HTTPS. Штатный токен хранится в базе, связан с одним пользователем и не имеет встроенного срока действия. Его можно отозвать удалением и выдать заново, но для нескольких устройств, областей доступа и ротации часто нужен более развитый пакет или собственная модель credentials.

Не логируйте заголовок Authorization и не возвращайте токены в диагностических ошибках.

#JWT через Simple JWT

JWT содержит подписанные claims. Access token обычно живёт недолго и передаётся к API, refresh token живёт дольше и используется только для выпуска новой пары.

from datetime import timedelta SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=10), "REFRESH_TOKEN_LIFETIME": timedelta(days=7), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "AUTH_HEADER_TYPES": ("Bearer",), }

Для blacklist нужно не только включить настройку, но и установить приложение с миграциями:

INSTALLED_APPS = [ # ... "rest_framework_simplejwt.token_blacklist", ]
from django.urls import path from rest_framework_simplejwt.views import ( TokenBlacklistView, TokenObtainPairView, TokenRefreshView, ) urlpatterns = [ path("auth/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("auth/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), path("auth/token/revoke/", TokenBlacklistView.as_view(), name="token_revoke"), ]

После изменения INSTALLED_APPS выполните миграции. Blacklist позволяет отозвать refresh/sliding tokens, но уже выданный access token обычно остаётся действительным до exp. Поэтому короткий срок access token — часть модели отзыва, а не косметическая настройка.

Ротация refresh token уменьшает ценность украденного старого токена, если старый сразу попадает в blacklist. Однако система должна определить поведение при повторном использовании, очистку истёкших записей и принудительный выход со всех устройств.

#Где хранить credentials клиента

Универсально безопасного места нет:

  • localStorage доступен JavaScript и поэтому особенно уязвим при XSS;
  • HttpOnly cookie не читается JavaScript, но браузер отправляет её автоматически, поэтому нужны CSRF-защита и продуманные cookie-атрибуты;
  • access token в памяти теряется при перезагрузке, зато не сохраняется надолго на устройстве.

Схема «refresh в HttpOnly cookie, access в памяти» распространена, но Simple JWT из коробки ориентирован на токены в запросе/ответе; cookie-вариант требует аккуратной собственной интеграции. Не отключайте CSRF только потому, что внутри cookie находится JWT.

Для мобильного приложения используют защищённое хранилище операционной системы. В любом варианте ограничивайте утечки через логи, аналитику, URL и сообщения об ошибках.

#OAuth 2.0 и OpenID Connect

OAuth 2.0 отвечает прежде всего за делегированную авторизацию: клиент получает ограниченный доступ к ресурсу. OpenID Connect добавляет поверх OAuth слой идентификации пользователя и id_token. Поэтому кнопка «Войти через провайдера» обычно означает OIDC, даже если в разговоре её называют OAuth-входом.

Если приложение само становится OAuth provider, рассмотрите поддерживаемый пакет вроде Django OAuth Toolkit. Если оно принимает вход от внешнего identity provider, используйте библиотеку OIDC/social-auth с проверкой state, nonce, redirect URI, issuer и audience. Не реализуйте протокол вручную по нескольким примерам из блога.

#Permissions

Permission-классы проверяются до выполнения основного метода view. Часто используются:

  • AllowAny — endpoint открыт;
  • IsAuthenticated — нужен аутентифицированный пользователь;
  • IsAdminUser — нужен user.is_staff;
  • IsAuthenticatedOrReadOnly — безопасные методы разрешены всем;
  • DjangoModelPermissions — используются модельные add, change, delete и связанные права.

Object permission отвечает за конкретную строку:

from rest_framework.permissions import BasePermission, SAFE_METHODS class IsOwnerOrReadOnly(BasePermission): def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS: return True return obj.owner_id == request.user.id
from rest_framework.permissions import IsAuthenticated class NoteViewSet(viewsets.ModelViewSet): permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] serializer_class = NoteSerializer def get_queryset(self): return Note.objects.filter(owner=self.request.user) def perform_create(self, serializer): serializer.save(owner=self.request.user)

Пример списка приватных заметок требует аутентификацию для всей view. Если безопасные методы открыты анонимно, обращение к request.user нужно обработать отдельно. Главное правило неизменно: has_object_permission() стандартно вызывается на detail-объекте, но не на каждой строке list. Список фильтрует get_queryset().

Проверяйте permissions для всех действий: list, retrieve, create, оба обновления, удаление и каждый @action. Право на родительский объект не всегда означает право привязать к нему любой дочерний объект.

#Throttling без ложного чувства безопасности

REST_FRAMEWORK = { "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "60/min", "user": "1000/hour", "login": "5/min", }, }
from rest_framework.throttling import ScopedRateThrottle class LoginView(APIView): throttle_classes = [ScopedRateThrottle] throttle_scope = "login"

Встроенный throttling DRF использует cache и не гарантирует строгое число запросов: операции счётчика могут быть неатомарными, а конкурентные запросы — немного превышать лимит. LocMemCache изолирован в каждом процессе, поэтому для нескольких workers нужен общий cache, например Redis.

IP-адрес тоже не является надёжной личностью: пользователи делят NAT, атакующий меняет адреса, а неверная конфигурация reverse proxy позволяет подделать исходный IP. Проверьте настройки proxy/NUM_PROXIES и применяйте разные ключи: IP, аккаунт, device/client и глобальный предел.

Троттлинг DRF полезен для политики использования и сдерживания случайной нагрузки. Защиту login/reset endpoint дополняют задержками, блокировками или challenge, наблюдаемостью и лимитами на edge/proxy. Объёмный DDoS должен останавливаться до Django.

#Практическая модель проверки

Для каждого защищённого endpoint тестируйте матрицу:

  1. анонимный запрос;
  2. пользователь без нужной роли;
  3. владелец объекта;
  4. пользователь той же организации, но не владелец;
  5. пользователь другой организации;
  6. staff/superuser — только если для него предусмотрено особое правило;
  7. истёкший, отозванный и некорректно подписанный credential;
  8. превышение лимита и восстановление после окна throttling.

Проверяйте не только HTTP-код, но и отсутствие данных в теле. Для чувствительных объектов ответ 404 вместо 403 иногда уменьшает возможность перебора идентификаторов, однако политика должна быть последовательной.

#Legacy и актуальные замечания

  • DRF включает Basic, Session и Token authentication. Digest, JWT и OAuth не являются встроенными классами ядра DRF.
  • Старое утверждение «JWT полностью stateless» слишком категорично: blacklist и проверка пользователя возвращают серверное состояние и запросы к хранилищу.
  • OAuth 2.0 и аутентификация пользователя — не синонимы; для identity обычно нужен OpenID Connect.
  • DRF throttling официально не следует считать защитой от brute force или DDoS; это прикладной лимит использования с неизбежной неточностью.

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

#Официальная документация

  • DRF: authentication
  • DRF: permissions
  • DRF: throttling
  • Simple JWT: blacklist

Далее: Кэширование с Redis