💻 Примеры: Auth-блок в действии
⚡ Ключевые примеры
- TokenAuthentication:
Authorization: Token abc123 - JWT логин: POST /api/token/ → {access, refresh}
- IsOwnerOrReadOnly: только владелец объекта может редактировать
- @receiver(post_save, sender=User): создать токен при регистрации
- send_mail() через консольный бэкенд для тестирования
Пример 1: TokenAuthentication — полный цикл
# settings.py
INSTALLED_APPS = [
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
# urls.py
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
path('books/', BookListView.as_view()),
]
# views.py
from rest_framework.generics import ListAPIView
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from .models import Book
from .serializers import BookSerializer
class BookListView(ListAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
Запрос через Postman: POST /api-token-auth/ с body (form-data): username, password → получаем {"token": "abc123..."}
GET /books/ с заголовком: Authorization: Token abc123...
GET /books/ с заголовком: Authorization: Token abc123...
Пример 2: JWT с httpOnly-куки (полный цикл)
# 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,
}
MIDDLEWARE = [
...
'first_app.middleware.JWTAuthenticationMiddleware',
]
# views.py — LoginView с сохранением в куки
from datetime import datetime, timezone
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import authenticate
from rest_framework_simplejwt.tokens import RefreshToken
from rest_framework.permissions import AllowAny
class LoginView(APIView):
permission_classes = [AllowAny]
def post(self, request, *args, **kwargs):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(request, username=username, password=password)
if user:
refresh = RefreshToken.for_user(user)
access_token = refresh.access_token
response = Response(status=status.HTTP_200_OK)
response.set_cookie(
key='access_token',
value=str(access_token),
httponly=True,
secure=False, # True на HTTPS
samesite='Lax',
expires=datetime.fromtimestamp(access_token['exp'], timezone.utc)
)
response.set_cookie(
key='refresh_token',
value=str(refresh),
httponly=True,
secure=False,
samesite='Lax',
expires=datetime.fromtimestamp(refresh['exp'], timezone.utc)
)
return response
return Response({'detail': 'Invalid credentials'}, status=status.HTTP_401_UNAUTHORIZED)
# views.py — LogoutView
class LogoutView(APIView):
def post(self, request, *args, **kwargs):
response = Response(status=status.HTTP_204_NO_CONTENT)
response.delete_cookie('access_token')
response.delete_cookie('refresh_token')
return response
Пример 3: Регистрация пользователя с JWT
# serializers.py
from rest_framework import serializers
from django.contrib.auth.models import User
class RegisterSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, min_length=8)
class Meta:
model = User
fields = ['username', 'password', 'email']
def create(self, validated_data):
return User.objects.create_user(
username=validated_data['username'],
password=validated_data['password'],
email=validated_data.get('email', '')
)
# views.py
from rest_framework_simplejwt.tokens import RefreshToken
class RegisterView(APIView):
permission_classes = [AllowAny]
def post(self, request):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
refresh = RefreshToken.for_user(user)
response = Response(
{'user': {'username': user.username, 'email': user.email}},
status=status.HTTP_201_CREATED
)
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=status.HTTP_400_BAD_REQUEST)
Пример 4: Кастомное разрешение IsOwnerOrReadOnly
# 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
# Расширенный вариант: владелец ИЛИ администратор
class IsAdminOrOwner(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in ('GET', 'HEAD', 'OPTIONS'):
return True
return request.user.is_staff or obj.owner == request.user
# views.py
from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .permissions import IsOwnerOrReadOnly
class BookDetailView(RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
Пример 5: Сигналы — автосоздание токена и email-уведомление
# signals.py
import logging
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from django.core.mail import send_mail
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
from .models import Book
logger = logging.getLogger(__name__)
# Автосоздание токена при регистрации пользователя
@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
if created:
Token.objects.create(user=instance)
# Уведомление администратора при создании книги
@receiver(post_save, sender=Book)
def notify_admin_on_book_create(sender, instance, created, **kwargs):
if created:
send_mail(
subject='New Book Created',
message=f'Book "{instance.title}" (id={instance.id}) has been created.',
from_email='admin@example.com',
recipient_list=['admin@example.com'],
)
# Логирование удаления книги
@receiver(post_delete, sender=Book)
def log_book_deletion(sender, instance, **kwargs):
logger.info(f'Book deleted: id={instance.id}, title={instance.title}')
# apps.py — обязательно для регистрации сигналов
from django.apps import AppConfig
class FirstAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'first_app'
def ready(self):
import first_app.signals # регистрируем обработчики
Пример 6: Кастомные разрешения модели
# models.py
class Genre(models.Model):
name = models.CharField(max_length=100, unique=True)
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')
# views.py
from rest_framework.permissions import DjangoModelPermissions
from .permissions import CanGetStatisticPermission
class GenreViewSet(ModelViewSet):
queryset = Genre.objects.all()
serializer_class = GenreSerializer
permission_classes = [DjangoModelPermissions, CanGetStatisticPermission]
Важно: После добавления Meta.permissions обязательно выполнить
python manage.py makemigrations && python manage.py migrate, иначе разрешение не появится в Django Admin.
Пример 7: Полная настройка OpenAPI 3
# settings.py
INSTALLED_APPS = [
...
'rest_framework',
'drf_spectacular',
]
REST_FRAMEWORK = {
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
SPECTACULAR_SETTINGS = {
'TITLE': 'My API',
'DESCRIPTION': 'API документация',
'VERSION': '1.0.0',
'SERVE_INCLUDE_SCHEMA': False,
}
# urls.py (проект)
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
SpectacularSwaggerView,
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('first_app.urls')),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]