📖 Теория: конспект блока Auth (Уроки 40–45)

🎯 Итоговое повторение К оглавлению урока

⚡ Краткий конспект блока Auth

  • Аутентификация — кто ты (личность); авторизация — что тебе можно (доступ). Порядок: request → аутентификация → авторизация.
  • Классы аутентификации: SessionAuthentication (куки+сессии), BasicAuthentication (Base64 в заголовке), TokenAuthentication (Token <key>), RemoteUserAuthentication (SSO).
  • JWT: header.payload.signature; SimpleJWT: pip install djangorestframework-simplejwt; ACCESS_TOKEN_LIFETIME, REFRESH_TOKEN_LIFETIME; эндпоинты /api/token/ и /api/token/refresh/.
  • httpOnly-куки: set_cookie(httponly=True, samesite='Lax'); JWTAuthenticationMiddleware для авто-подстановки токена в заголовок.
  • Регистрация с JWT: RegisterSerializer → create_user() → RefreshToken.for_user() → set_jwt_cookies().
  • Разрешения: AllowAny / IsAuthenticated / IsAdminUser / IsAuthenticatedOrReadOnly; DEFAULT_PERMISSION_CLASSES в settings.py.
  • Объектный уровень: BasePermission.has_object_permission(request, view, obj) → IsOwnerOrReadOnly.
  • Разрешения модели: DjangoModelPermissions; Meta.permissions; request.user.has_perm('app.codename').
  • Сигналы: pre_save, post_save, post_delete, m2m_changed; @receiver(signal, sender=Model); AppConfig.ready() → import signals.
  • Email: send_mail(subject, body, from, [to]); EMAIL_BACKEND = console / smtp.
  • Swagger: drf-yasg; get_schema_view() → /swagger/ и /redoc/.

1. Аутентификация и авторизация (Урок 40)

Аутентификация — процесс проверки подлинности пользователя: кто ты? DRF перебирает все классы из DEFAULT_AUTHENTICATION_CLASSES и устанавливает request.user.

Авторизация — процесс определения, что аутентифицированный пользователь может делать. DRF применяет DEFAULT_PERMISSION_CLASSES и вызывает has_permission() / has_object_permission().

ПараметрАутентификацияАвторизация
ВопросКто ты?Что тебе можно?
КогдаПервой (перед авторизацией)После аутентификации
Провал401 Unauthorized403 Forbidden
DRF-настройкаDEFAULT_AUTHENTICATION_CLASSESDEFAULT_PERMISSION_CLASSES

Порядок проверки в DRF

  1. Клиент отправляет запрос.
  2. DRF перебирает authentication_classes → устанавливает request.user.
  3. DRF перебирает permission_classes → разрешает или отклоняет запрос.

2. Классы аутентификации (Урок 40)

SessionAuthentication

Использует стандартный Django-механизм сессий и куки. Подходит для web-приложений, не для API. Требует CSRF-токен для небезопасных методов.

BasicAuthentication

Передаёт логин:пароль в каждом запросе в заголовке Authorization: Basic <base64>. Не подходит без HTTPS — credentials видны в каждом запросе. Для тестирования.

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.BasicAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

TokenAuthentication

Клиент хранит токен (выдаётся сервером при логине) и отправляет его в каждом запросе: Authorization: Token <key>. Токены хранятся в БД в таблице authtoken_token.

# settings.py
INSTALLED_APPS = [..., 'rest_framework.authtoken']
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}
# urls.py
from rest_framework.authtoken.views import obtain_auth_token
path('api-token-auth/', obtain_auth_token)

RemoteUserAuthentication

Для SSO-интеграции. Аутентификация на основе заголовка REMOTE_USER, установленного веб-сервером (nginx, Apache). Менее гибок для стандартных API.

3. JWT и SimpleJWT (Уроки 40, 44)

JWT (JSON Web Token) — самодостаточный токен: header.payload.signature, каждый сегмент в Base64url. Не требует хранения в БД (stateless).

Структура JWT

  • header: тип токена + алгоритм подписи (HS256)
  • payload: claims: sub (user id), iat (issued at), exp (expiry)
  • signature: HMAC(header + payload, SECRET_KEY)

SimpleJWT

pip install djangorestframework-simplejwt

# settings.py
from datetime import timedelta
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
    'AUTH_HEADER_TYPES': ('Bearer',),
}

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
path('api/token/', TokenObtainPairView.as_view()),
path('api/token/refresh/', TokenRefreshView.as_view()),

JWT в httpOnly-куки (Урок 44)

Токены сохраняются в httpOnly-куки при логине. JWTAuthenticationMiddleware извлекает токен из куки и подставляет в заголовок Authorization при каждом запросе.

Регистрация с JWT

class RegisterView(APIView):
    def post(self, request):
        serializer = RegisterSerializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            refresh = RefreshToken.for_user(user)
            response = Response({'user': user.username}, status=201)
            response.set_cookie('access_token', str(refresh.access_token), httponly=True)
            response.set_cookie('refresh_token', str(refresh), httponly=True)
            return response
        return Response(serializer.errors, status=400)

4. Разрешения (Уроки 40, 42)

Встроенные классы

КлассКто получает доступ
AllowAnyВсе (аутентифицированные и нет)
IsAuthenticatedТолько аутентифицированные (иначе 401)
IsAdminUserТолько is_staff=True (иначе 403)
IsAuthenticatedOrReadOnlyЗапись — авторизованным; чтение — всем

request.user

# Автоматическое добавление владельца объекта
class BookViewSet(ModelViewSet):
    queryset = Book.objects.all()
    permission_classes = [IsAuthenticated]

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

    def get_queryset(self):
        return Book.objects.filter(owner=self.request.user)

Объектный уровень: BasePermission

# permissions.py
from rest_framework.permissions import BasePermission

class IsOwnerOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in ('GET', 'HEAD', 'OPTIONS'):
            return True
        return obj.owner == request.user

DjangoModelPermissions и кастомные разрешения модели

# models.py
class Genre(models.Model):
    name = models.CharField(max_length=100)
    class Meta:
        permissions = [('can_get_statistic', 'Can get genres statistic')]

# permissions.py
class CanGetStatisticPermission(BasePermission):
    def has_permission(self, request, view):
        return request.user.has_perm('first_app.can_get_statistic')

5. Сигналы Django (Уроки 42, 45)

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

СигналКогда срабатывает
pre_saveПеред Model.save()
post_saveПосле Model.save(); created=True при создании
pre_deleteПеред Model.delete()
post_deleteПосле Model.delete()
m2m_changedПри изменении ManyToMany-отношения
# signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User

@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

# apps.py
class FirstAppConfig(AppConfig):
    name = 'first_app'
    def ready(self):
        import first_app.signals

6. Email (Урок 45)

# Консольный бэкенд для тестирования
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

# SMTP Gmail (продакшн)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your_email@gmail.com'
EMAIL_HOST_PASSWORD = 'your_app_password'

# Отправка письма
from django.core.mail import send_mail
send_mail('Subject', 'Body text', 'from@example.com', ['to@example.com'])

7. Swagger / drf-yasg (Урок 43)

pip install drf-yasg

# settings.py
INSTALLED_APPS = [..., 'drf_yasg']

# urls.py
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
    openapi.Info(title="API", default_version='v1'),
    public=True,
    permission_classes=[permissions.AllowAny],
)

urlpatterns += [
    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0)),
    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0)),
]