✅ Решения практикума 9
Полный разбор всех 13 задач с объяснением по источнику PrfS9
⚡ Все решения — быстрые ссылки
- 1.1 — BasicAuthentication в settings.py
- 1.2 — BasicAuthentication в ProductListCreateView
- 1.3 — Тест BasicAuth через Postman
- 2.1 — TokenAuthentication в settings.py
- 2.2 — obtain_auth_token в urls.py
- 2.3 — TokenAuthentication в ProductListCreateView
- 2.4 — Тест TokenAuth через Postman
- 3.1 — JWTAuthentication в settings.py
- 3.2 — Маршруты JWT в urls.py
- 3.3 — JWTAuthentication в OrderListCreateView
- 3.4 — Тест JWT через Postman
- 4.1 — IsAuthenticatedOrReadOnly для ProductDetail
- 4.2 — IsAdminUser для SupplierViewSet
- 4.3 — Разрешения для остальных представлений
Задание 1: BasicAuthentication
Решение 1.1 — Настройка BasicAuthentication
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
Объяснение: Здесь настраивается глобальный механизм аутентификации. Все представления, у которых не переопределён
authentication_classes, будут требовать BasicAuthentication. DEFAULT_PERMISSION_CLASSES с IsAuthenticated означает, что неаутентифицированные запросы получают 403/401.
Решение 1.2 — Применение BasicAuthentication к представлению
# views.py
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
class ProductListCreateView(ListCreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
authentication_classes = [BasicAuthentication]
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.request.method == 'GET':
return ProductSerializer
return ProductCreateUpdateSerializer
Объяснение: Атрибут
authentication_classes переопределяет глобальную настройку из settings.py для конкретного представления. Это даёт гибкость: разные представления могут использовать разные механизмы аутентификации. get_serializer_class() выбирает разные сериализаторы для GET и POST.
Решение 1.3 — Получение доступа через Postman
# Вариант 1: credentials в URL (только для тестирования!)
http://username:password@127.0.0.1:8000/products/
# Вариант 2: заголовок Authorization (правильно)
# В Postman: вкладка Authorization -> Basic Auth
# Ввести Username и Password — Postman сам сформирует заголовок:
# Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Объяснение: Basic Auth кодирует строку
username:password в Base64. Это не шифрование — Base64 тривиально декодируется. Поэтому BasicAuthentication безопасно только с HTTPS. Для тестирования локально Postman делает Base64-кодирование автоматически.
Задание 2: TokenAuthentication
Решение 2.1 — Настройка TokenAuthentication
# settings.py
INSTALLED_APPS = [
# ...
'rest_framework',
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
],
}
# Выполните миграции
# python manage.py migrate
Объяснение: Пакет
rest_framework.authtoken добавляет модель Token в базу данных. После добавления в INSTALLED_APPS обязательно нужно выполнить python manage.py migrate — без этого таблица authtoken_token не создастся, и при первом запросе токена будет ошибка.
Решение 2.2 — Получение токена (obtain_auth_token)
# urls.py
from django.urls import path
from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [
path('api-token-auth/', obtain_auth_token, name='api_token_auth'),
]
Объяснение:
obtain_auth_token — встроенное в DRF представление для получения токена. Принимает POST-запрос с username и password в теле, возвращает {"token": "..."}. Токен привязывается к пользователю и хранится в БД. При повторном запросе возвращает тот же токен.
Решение 2.3 — Применение TokenAuthentication к представлению
# views.py
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
class ProductListCreateView(ListCreateAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.request.method == 'GET':
return ProductSerializer
return ProductCreateUpdateSerializer
Объяснение: Структура аналогична BasicAuthentication — только класс меняется с
BasicAuthentication на TokenAuthentication. Остальная логика (queryset, permission, get_serializer_class) не меняется. Это демонстрирует, что DRF разделяет аутентификацию и бизнес-логику.
Решение 2.4 — Получение доступа с помощью токена
# Шаг 1: получить токен
# POST http://127.0.0.1:8000/api-token-auth/
# Body: {"username": "admin", "password": "your_password"}
# Ответ: {"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"}
# Шаг 2: использовать токен в запросе
# GET http://127.0.0.1:8000/products/
Authorization: Token your_token
Объяснение: Обратите внимание на формат заголовка: ключевое слово —
Token (с большой буквы), затем пробел, затем значение токена. Не Bearer, не Basic — именно Token. В Postman: вкладка Authorization → Bearer Token (вводим токен без слова Token) или Headers → Authorization → "Token ваш_токен".
Задание 3: JWT-аутентификация
Решение 3.1 — Настройка JWTAuthentication
# Установка Simple JWT
# pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta
INSTALLED_APPS = [
# ...
'rest_framework_simplejwt',
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
}
Объяснение: Simple JWT — сторонний пакет, требует отдельной установки. В
SIMPLE_JWT настраивается время жизни токенов. Access-токен — короткоживущий (5 минут), чтобы снизить риск при перехвате. Refresh-токен — долгоживущий (1 день), позволяет получить новый access без повторного ввода пароля.
Решение 3.2 — Маршруты для JWT
# urls.py
from django.urls import path
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Объяснение:
TokenObtainPairView принимает логин/пароль и возвращает пару: access + refresh токены. TokenRefreshView принимает refresh-токен и возвращает новый access-токен. Оба маршрута должны быть открыты (AllowAny), иначе пользователи не смогут получить токены.
Решение 3.3 — Применение JWT к представлению Order
# views.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class OrderListCreateView(ListCreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.request.method == 'GET':
return OrderSerializer
return OrderCreateUpdateSerializer
Объяснение: Паттерн тот же — меняется только импорт и класс аутентификации. Здесь используется представление для
Order вместо Product. DRF проверяет JWT, декодируя его подпись — без обращения к БД для access-токена.
Решение 3.4 — Получение доступа с помощью JWT
# Шаг 1: получить JWT
# POST http://127.0.0.1:8000/api/token/
# Body: {"username": "admin", "password": "your_password"}
# Ответ: {"access": "eyJhbGci...", "refresh": "eyJhbGci..."}
# Шаг 2: использовать access-токен
# GET http://127.0.0.1:8000/orders/
Authorization: Bearer your_token
# Шаг 3: обновить access-токен когда истёк
# POST http://127.0.0.1:8000/api/token/refresh/
# Body: {"refresh": "eyJhbGci..."}
# Ответ: {"access": "новый_access_token"}
Объяснение: Ключевое отличие от Token: здесь
Bearer вместо Token в заголовке. Это стандарт OAuth 2.0 / RFC 6750. JWT не требует хранения в БД при проверке — сервер просто проверяет подпись и срок жизни токена.
Задание 4: Разрешения на уровне представления
Решение 4.1 — IsAuthenticatedOrReadOnly для ProductDetail
# views.py
from rest_framework.permissions import IsAuthenticatedOrReadOnly
class ProductDetailDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
queryset = ProductDetail.objects.all()
serializer_class = ProductDetailSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def get_serializer_class(self):
if self.request.method == 'GET':
return ProductDetailSerializer
return ProductDetailCreateUpdateSerializer
Объяснение:
IsAuthenticatedOrReadOnly разрешает GET/HEAD/OPTIONS для всех (включая анонимов), но требует аутентификации для PUT/PATCH/DELETE. Это классический паттерн для публичных API с защищёнными операциями записи.
Решение 4.2 — IsAdminUser для SupplierViewSet
# views.py
from rest_framework.permissions import IsAdminUser
class SupplierViewSet(viewsets.ModelViewSet):
queryset = Supplier.objects.all()
serializer_class = SupplierSerializer
permission_classes = [IsAdminUser]
Объяснение:
IsAdminUser проверяет флаг is_staff=True у пользователя. Управление поставщиками — административная операция, поэтому логично ограничить доступ только для персонала. Обычные аутентифицированные пользователи получат 403 Forbidden.
Решение 4.3 — Разрешения для остальных представлений
Задание 4.3 открытое — каждая команда обсуждает и выбирает разрешения исходя из логики приложения. Типичный вариант для интернет-магазина:
# Возможный вариант для интернет-магазина
# Категории: публичное чтение, только авторизованные пишут
class CategoryViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticatedOrReadOnly]
# Продукты: публичное чтение, только авторизованные пишут
class ProductListCreateView(ListCreateAPIView):
permission_classes = [IsAuthenticatedOrReadOnly]
# Заказы: только авторизованные (покупатель видит свои заказы)
class OrderListCreateView(ListCreateAPIView):
permission_classes = [IsAuthenticated]
# Покупатели: только авторизованные
class CustomerListCreateView(ListCreateAPIView):
permission_classes = [IsAuthenticated]
# Адреса: только авторизованные
class AddressViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
⚠️ Проверить по документации: выбор permission-классов зависит от бизнес-требований конкретного приложения. Данный пример — один из возможных вариантов, не единственно правильный.