📖 Теория: конспект блока 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 Unauthorized | 403 Forbidden |
| DRF-настройка | DEFAULT_AUTHENTICATION_CLASSES | DEFAULT_PERMISSION_CLASSES |
Порядок проверки в DRF
- Клиент отправляет запрос.
- DRF перебирает
authentication_classes→ устанавливаетrequest.user. - 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)),
]