Django: DELETE падал в 403 — сессия без X-CSRFToken
SPA аутентифицируется сессионной кукой и не прикладывает X-CSRFToken, а
SessionAuthentication из DRF требует его на любом небезопасном методе:
DELETE /api/core/videos/<id>/ -> 403
{"detail": "CSRF Failed: CSRF token missing."}
Проверено на контуре, тремя запросами по несуществующему id (403 = отказ
авторизации, 404 = авторизация прошла):
Bearer-токен -> 404 CSRF не при чём
сессия + X-CSRFToken -> 404 проходит
сессия без X-CSRFToken -> 403 ровно наша ошибка
без авторизации -> 401
То есть бэкенд исправен, ломается ровно сочетание "кука без заголовка".
С Bearer до SessionAuthentication дело не доходит: simple_jwt стоит в
DEFAULT_AUTHENTICATION_CLASSES раньше.
Ставим подкласс с пустым enforce_csrf на то же пятое место. Цена — с
cookie-запросов снимается защита от CSRF. Основной вектор закрыт самими
куками (sessionid и csrftoken выставляются с SameSite=Lax, браузер не
пошлёт их при кросс-сайтовом DELETE); остаётся щель в соседях по домену,
поскольку SameSite смотрит на site, а не на origin.
Импорт rest_framework в модуле настроек безопасен не всегда — обычно так
ловят AppRegistryNotReady. Здесь проверено запуском django.setup() внутри
работающего пода backend: setup проходит, строка пути к классу резолвится.
This commit is contained in:
parent
75d1fba14a
commit
a67ab33e5d
@ -4,7 +4,7 @@
|
||||
# (331 строка python), kustomize умеет заменить его только целиком. Ровно та же
|
||||
# причина, что у nginx-configmap.yaml рядом.
|
||||
#
|
||||
# ЧТО ИЗМЕНЕНО — две строки, обе про домен sarex.contour.infra.sarex.tech,
|
||||
# ЧТО ИЗМЕНЕНО — две строки про домен sarex.contour.infra.sarex.tech,
|
||||
# принадлежащий ДРУГОМУ контуру:
|
||||
#
|
||||
# HOST — по нему backend строит АБСОЛЮТНЫЕ ссылки на медиа.
|
||||
@ -14,6 +14,11 @@
|
||||
# CSRF_TRUSTED_ORIGINS — без нашего домена django отклонял бы POST-формы
|
||||
# админки с ошибкой проверки Referer.
|
||||
#
|
||||
# И один блок сверх того: CsrfExemptSessionAuthentication в
|
||||
# DEFAULT_AUTHENTICATION_CLASSES вместо штатного SessionAuthentication —
|
||||
# из-за него SPA получала 403 на любое удаление. Обоснование и цена решения
|
||||
# расписаны прямо над классом.
|
||||
#
|
||||
# Больше в файле ничего не менялось: сравнивать с base удобно обычным diff.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@ -215,6 +220,40 @@ data:
|
||||
},
|
||||
}
|
||||
|
||||
# Сессионная аутентификация без проверки CSRF.
|
||||
#
|
||||
# SPA платформы аутентифицируется сессионной кукой и не прикладывает
|
||||
# заголовок X-CSRFToken, а SessionAuthentication из DRF требует его на
|
||||
# любом небезопасном методе. Наружу это выглядело так:
|
||||
# DELETE /api/core/videos/<id>/ → 403
|
||||
# {"detail": "CSRF Failed: CSRF token missing."}
|
||||
#
|
||||
# Проверено на контуре: тот же запрос с X-CSRFToken проходит, и он же с
|
||||
# заголовком Authorization: Bearer проходит тоже — до SessionAuthentication
|
||||
# дело в этом случае просто не доходит, потому что simple_jwt в списке ниже
|
||||
# стоит раньше. То есть бэкенд исправен, чинить нужно ровно сочетание
|
||||
# "кука без заголовка".
|
||||
#
|
||||
# ЧЕМ ПЛАТИМ: с запросов, авторизованных кукой, снимается защита от CSRF.
|
||||
# Основной вектор закрыт самими куками — sessionid и csrftoken выставляются
|
||||
# с SameSite=Lax, и браузер не пошлёт их при кросс-сайтовом DELETE. Щель
|
||||
# остаётся одна: SameSite смотрит на site, а не на origin, поэтому соседи
|
||||
# по домену (minio., rabbitmq., dashboard.) считаются "своими", а в MinIO
|
||||
# лежит пользовательский контент.
|
||||
#
|
||||
# Импорт DRF прямо в модуле настроек безопасен НЕ всегда — обычно так и
|
||||
# ловят AppRegistryNotReady. Здесь проверено запуском django.setup() внутри
|
||||
# работающего пода: rest_framework.authentication тянет только
|
||||
# django.contrib.auth и rest_framework.exceptions, и ни один из них к
|
||||
# моделям на уровне модуля не обращается.
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
|
||||
|
||||
class CsrfExemptSessionAuthentication(SessionAuthentication):
|
||||
def enforce_csrf(self, request):
|
||||
return
|
||||
|
||||
|
||||
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': (
|
||||
'rest_framework.pagination.LimitOffsetPagination' ),
|
||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
||||
@ -225,7 +264,7 @@ data:
|
||||
'rest_framework.authentication.RemoteUserAuthentication',
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
'rest_framework.authentication.BasicAuthentication',
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
'config.settings.production.CsrfExemptSessionAuthentication',
|
||||
'sarex.authentication.backends.JWTAuthentication' ],
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.IsAuthenticated', ] }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user