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 умеет заменить его только целиком. Ровно та же
|
# (331 строка python), kustomize умеет заменить его только целиком. Ровно та же
|
||||||
# причина, что у nginx-configmap.yaml рядом.
|
# причина, что у nginx-configmap.yaml рядом.
|
||||||
#
|
#
|
||||||
# ЧТО ИЗМЕНЕНО — две строки, обе про домен sarex.contour.infra.sarex.tech,
|
# ЧТО ИЗМЕНЕНО — две строки про домен sarex.contour.infra.sarex.tech,
|
||||||
# принадлежащий ДРУГОМУ контуру:
|
# принадлежащий ДРУГОМУ контуру:
|
||||||
#
|
#
|
||||||
# HOST — по нему backend строит АБСОЛЮТНЫЕ ссылки на медиа.
|
# HOST — по нему backend строит АБСОЛЮТНЫЕ ссылки на медиа.
|
||||||
@ -14,6 +14,11 @@
|
|||||||
# CSRF_TRUSTED_ORIGINS — без нашего домена django отклонял бы POST-формы
|
# CSRF_TRUSTED_ORIGINS — без нашего домена django отклонял бы POST-формы
|
||||||
# админки с ошибкой проверки Referer.
|
# админки с ошибкой проверки Referer.
|
||||||
#
|
#
|
||||||
|
# И один блок сверх того: CsrfExemptSessionAuthentication в
|
||||||
|
# DEFAULT_AUTHENTICATION_CLASSES вместо штатного SessionAuthentication —
|
||||||
|
# из-за него SPA получала 403 на любое удаление. Обоснование и цена решения
|
||||||
|
# расписаны прямо над классом.
|
||||||
|
#
|
||||||
# Больше в файле ничего не менялось: сравнивать с base удобно обычным diff.
|
# Больше в файле ничего не менялось: сравнивать с base удобно обычным diff.
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
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 = { 'DEFAULT_PAGINATION_CLASS': (
|
||||||
'rest_framework.pagination.LimitOffsetPagination' ),
|
'rest_framework.pagination.LimitOffsetPagination' ),
|
||||||
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
|
||||||
@ -225,7 +264,7 @@ data:
|
|||||||
'rest_framework.authentication.RemoteUserAuthentication',
|
'rest_framework.authentication.RemoteUserAuthentication',
|
||||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
'rest_framework.authentication.BasicAuthentication',
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
'config.settings.production.CsrfExemptSessionAuthentication',
|
||||||
'sarex.authentication.backends.JWTAuthentication' ],
|
'sarex.authentication.backends.JWTAuthentication' ],
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
'DEFAULT_PERMISSION_CLASSES': [
|
||||||
'rest_framework.permissions.IsAuthenticated', ] }
|
'rest_framework.permissions.IsAuthenticated', ] }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user