Як відправити токен CSRF за допомогою Django API та Flutter-web фронтенду? HeaderDisallowedByPreflightResponse [дублікат]
У мене є python/django web API з однією кінцевою точкою, назвемо її /api/v1/form
. Цей API викликається з фронтенд-додатку Flutter-web. Наразі я використовую наступну конфігурацію, яка відключає перевірку токенів CSRF, і вона працює :
requirements.txt
Django==5.1.7
django-cors-headers==4.7.0
webserver/settings.py
...
ALLOWED_HOSTS = ["localhost"]
CORS_ALLOWED_ORIGINS = ["http://localhost:8001"] # flutter dev port
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'webserver',
]
...
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
webserver/urls.py
from django.urls import path
import webserver.views
urlpatterns = [
path('api/v1/form', webserver.views.api_v1_form, name="api_v1_form"),
]
...
webserver/views.py
from django.http import HttpResponse, HttpResponseBadRequest
def api_v1_form(request):
if request.method == "POST":
process_request(request.body)
return HttpResponse("request successfully processed.")
return HttpResponseBadRequest("Expected a POST request")
flutter/lib/page_form.dart
Future<int> sendForm(MyData data) async {
final response = await http.post(
Uri.parse("http://localhost:8000/api/v1/form"),
body: data.toString(),
);
return response.statusCode;
}
Ось що я не розумію: якщо я відключаю пакет CORS, щоб просто використовувати ванільний сервер Django, то виявляється, що я можу надсилати запити до API, але не можу отримати відповідь. Чому так відбувається ?
?Нижче наведено конфігурацію, яка використовується для отримання токену CSRF та його використання у запитах.
settings.py
ALLOWED_HOSTS = ["localhost"]
CSRF_TRUSTED_ORIGINS = ["http://localhost:8001", "http://localhost:8000"]
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'webserver',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
Змінивши лише settings.py, я отримую відповідь 403 forbidden
.
views.py
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def api_v1_form(request):
... # unchanged
Відповідь все ще 403 forbidden
Нарешті, я спробував додати API /api/v1/token
для відправки токена і повторного використання його в запиті.
views.py
def api_v1_token(request):
if request.method == "GET":
return HttpResponse(get_token(request))
return HttpResponseBadRequest()
@ensure_csrf_cookie
def api_v1_form(request):
if request.method == "OPTIONS": # POST -> OPTIONS
# ...
page_form.dart
Future<int> envoyerFormulaire(MyData data) async {
final tokenResponse = await http.get(
Uri.parse("$apiUrlBase/token"),
);
final token = tokenResponse.body.toString();
Uri uriPost = Uri.parse("$apiUrlBase/form");
final dataResponse = await http.post(
uriPost,
body: data.toString(),
headers: {
"X-CSRFToken": token,
}
);
return dataResponse.statusCode;
}
Проте, використовуючи інспектор, я отримую помилку, яка вказує на відсутність заголовка Access-Control-Allow-Origin
при спробі отримати токен. Тому я додаю
views.py
def api_v1_token(request):
if request.method == "GET":
response = HttpResponse(get_token(request))
response["Access-Control-Allow-Origin"] = "http://localhost:8001"
return response
return HttpResponseBadRequest()
def api_v1_form(request: HttpResponse):
if request.method == "OPTIONS":
# ...
response["Access-Control-Allow-Origin"] = "http://localhost:8001"
return response
Тепер я можу отримати токен, але POST запит видає помилку PreflightMissingAllowOriginHeader
. Я розумію, що я повинен додати заголовок, але хіба я не додав Access-Control-Allow-Origin
до всіх запитів ?
Більше того, я бачу, що для POST запиту є два запити: preflight і fetch, preflight повертає помилку 500 internal
, тому що django намагається розібрати вміст порожнього запиту. Це можна виправити, додавши фільтр
views.py
if request.method == "OPTIONS": # preflight
res = HttpResponse()
res["Access-Control-Allow-Origin"] = "http://localhost:8001"
return res
if request.method == "GET":
# normal processing after that
А тепер передпольотна перевірка повертає 200 OK
. Але я отримую помилку CORS на запит вибірки (HeaderDisallowedByPreflightResponse
).
Тепер я застряг. Здається, я чогось не розумію в заголовках і запитах CORS: мені не вистачає заголовків? з якими значеннями? і чи очікується, що програма обробить дані з попереднього польоту і поверне результат у наступному GET-запиті замість одного POST-запиту? Чи є це очікуваною поведінкою ?
?