Vue.js 向 Django 发送 POST/DELETE 请求时出现“禁止访问(CSRF cookie not set.)”错误。

5

我一直尝试在我的Vue前端向Django后端发送POST或DELETE请求。

我的Vue.js运行在localhost:3000上,而Django运行在localhost:8000上。 我使用django-cors-headers设置了CORS,并且可以进行GET请求。但是,一旦我尝试进行DELETE或POST,就会得到这个错误:

Forbidden (CSRF cookie not set.)

我明白我需要在我的请求头中传递一个CSRF令牌,我已经有了:

    deleteImage() {
      const url = this.serverURL + 'images/delete/' + this.image_data.pk;

      const options = {
        method: "DELETE",
        headers: {'X-CSRFToken': this.CSRFtoken}
      };
      fetch(url, options)
        .then(response => {
          console.log(response);
          if (response.ok){ 
            // if response is successful, do something
          }
        })
        .catch(error => console.error(error));
    }

我从GET请求中获取this.CSRFtoken,如果我使用Django文档中所示的方法,令牌将保持不变。
我的Django settings.py如下:
rom pathlib import Path
import os

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '***'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'corsheaders',
    'serveImages.apps.ServeimagesConfig',
    'django_admin_listfilter_dropdown',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
]

CORS_ALLOWED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000"
]


CSRF_TRUSTED_ORIGINS = [
    "http://localhost:3000",
    "http://127.0.0.1:3000"
]

我知道默认情况下django-cors-headers允许头文件X-CSRFToken

我已经查看了StackOverflow上关于这个主题的所有先前问题,但似乎没有任何解决办法。

更多上下文: views.py

from django.http import JsonResponse
import os
from django.conf import settings
from django.middleware import csrf

from .models import Image


def get_csrf_token(request):
    token = csrf.get_token(request)
    return token
    # return JsonResponse({'CSRFtoken': token})

def index(request, dataset, class_label):
    payload = {}

    images_folder_url = os.path.join('static', 'images', dataset, class_label.lower())
    payload['base_url'] = images_folder_url

    data_query = Image.objects.filter(dataset__name=dataset, label__name=class_label).values('pk', 'path', 'isIncluded')
    payload['image_data'] = list(data_query)
    payload['number_of_images'] = len(payload['image_data'])
    payload['CSRFtoken'] = get_csrf_token(request)

    return JsonResponse(payload)

def delete_image(request, img_pk):
    print(request)
    # Just for testing
    return JsonResponse({'status': '200'})

urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('get-token', views.get_csrf_token, name='CSRFtoken'),
    path('images/<str:dataset>/<str:class_label>', views.index, name='index'),
    path('images/delete/<int:img_pk>', views.delete_image, name='delete_image'),
]
1个回答

6
好的,我之前经历过这样的问题,可以说是非常令人沮丧。如果我完全诚实地说,那是因为我不理解所有设置的动机或交互作用。由于没有时间阅读所有文档,有些内容我仍然不理解。 总之,可能会有很多潜在的问题,我将列举一些我遇到的,你可能也在处理这些问题。 首先,请确保通过相同的url运行Vue.js应用程序。也就是说,如果你在127.0.0.1:8080上运行django,则你的Vue应用程序应该在127.0.0.1:3000上运行,而不是localhost。这可能不是你目前的问题,但它可能会让你感到困惑。如果你最终的设置是从与后端不同的域名提供前端服务,则你可能需要调整一些设置。 接下来,启用CORS以允许在跨站点http请求中包含cookie。这可能不是你当前的问题,但它可能会成为下一个问题。
# https://github.com/adamchainz/django-cors-headers#cors_allow_credentials
CORS_ALLOW_CREDENTIALS = True

最后,针对您当前的问题,首先我建议您在前端使用axios来发送请求。

import axios from 'axios'

axios.defaults.xsrfHeaderName = 'x-csrftoken'
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.withCredentials = true

let djangoURL = 'http://127.0.0.1:8000'
// `timeout` specifies the number of milliseconds before the request times out.
// Because we enable Django Debug Toolbar for local development, there is often
// a processing hit. This can also be tremendously bad with unoptimized queries.
let defaultTimeout = 30000
if (process.env.PROD) {
  djangoURL = 'https://##.##.#.##:###'
  defaultTimeout = 10000
}
axios.defaults.baseURL = djangoURL
axios.defaults.timeout = defaultTimeout

const api = axios.create()
export { api }

defaultTimeout(默认超时设置)和条件本地与生产环境的评估完全是可选的,它们只是一些好用的功能。发送请求应该像这样:

new Promise((resolve, reject) => {
    api.delete('images/delete/' + this.image_data.pk).then(response => {
      // console.log(response)
      resolve(response)
    }, error => {
      // console.log(error)
      reject(error)
    })
  })

最后一件事,将仅限HTTP的cookie设置为false

# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
CSRF_COOKIE_HTTPONLY = False

有时候帮助我的一个好的参考例子是这个博客: https://briancaffey.github.io/2021/01/01/session-authentication-with-django-django-rest-framework-and-nuxt 和这个博客: https://testdriven.io/blog/django-spa-auth/ 第二个设置了一些不适用的设置,而且是在react中,但仍然是视图设置的好起点。 您最终会想要合并身份验证,因此现在选择;会话还是jwt。我选择了基于会话的身份验证,但有些人在想要通过第三方像0auth之类的进行身份验证时使用jwt。

下面是我用于身份验证的视图示例:

import json
# import logging

from django.contrib.auth import authenticate, login, logout
from django.http import JsonResponse
from django.middleware.csrf import get_token
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_POST
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class SessionView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAuthenticated]

    @staticmethod
    def get(request, format=None):
        return JsonResponse({'isAuthenticated': True})


class WhoAmIView(APIView):
    authentication_classes = [SessionAuthentication, BasicAuthentication]
    permission_classes = [IsAuthenticated]

    @staticmethod
    def get(request, format=None):
        return JsonResponse({'username': request.user.username})


@ensure_csrf_cookie
def get_csrf(request):
    response = JsonResponse({'detail': 'CSRF cookie set'})
    response['X-CSRFToken'] = get_token(request)
    return response


@require_POST
def login_view(request):
    data = json.loads(request.body)
    username = data.get('username')
    password = data.get('password')

    if username is None or password is None:
        return JsonResponse({'detail': 'Please provide username and password.'}, status=400)

    user = authenticate(username=username, password=password)

    if user is None:
        return JsonResponse({'detail': 'Invalid credentials.'}, status=400)

    login(request, user)
    return JsonResponse({'detail': 'Successfully logged in.'})


def logout_view(request):
    if not request.user.is_authenticated:
        return JsonResponse({'detail': 'You\'re not logged in.'}, status=400)

    logout(request)
    return JsonResponse({'detail': 'Successfully logged out.'})

urls.py

urlpatterns = [
    path('csrf/', views.get_csrf, name='api-csrf'),
    path('login/', views.login_view, name='api-login'),
    path('logout/', views.logout_view, name='api-logout'),
    path('session/', views.SessionView.as_view(), name='api-session'),  # new
    path('whoami/', views.WhoAmIView.as_view(), name='api-whoami'),  # new
]

编辑: 如果你使用的是Cookiecutter,还有一个需要注意的地方就是api端点的限制器。在base.py配置文件中有以下代码,如果你没有将所有端点设置在这个url下,它们会出现这种错误。

# django-cors-headers - https://github.com/adamchainz/django-cors-headers#setup
CORS_URLS_REGEX = r"^/api/.*$"

因此,要么禁用它,要么将您的身份验证放在此URL下。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接