【问题标题】:Forbidden (CSRF cookie not set.) when sending POST/DELETE request from Vue.js to Django从 Vue.js 向 Django 发送 POST/DELETE 请求时禁止(未设置 CSRF cookie。)
【发布时间】:2021-09-16 05:29:11
【问题描述】:

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

我在 localhost:3000 上运行 Vue.js,在 localhost:8000 上运行 Django。 我已经使用django-cors-headers 设置了 CORS,并且能够获取请求。但是,一旦我尝试 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 docs 中显示的方法,则令牌是相同的。

我的 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'),
]

【问题讨论】:

    标签: django vue.js django-csrf csrf-token django-cors-headers


    【解决方案1】:

    好的,所以我之前经历过这场战斗,至少可以说令人沮丧。如果我完全诚实,那是因为我不了解所有相关设置的推动力或相互作用。由于没有时间阅读所有文档,因此仍然不要这样做。 无论如何,有很多潜在的陷阱,我将介绍一些我遇到的你可能会在这里处理的问题。

    首先,确保您通过相同的 url 运行 Vue.js 应用程序。例如,如果您在 127.0.0.1:8080 上运行 django,那么您的 Vue 应用程序应该在 127.0.0.1:3000 而不是 localhost 上运行。这可能不是你当前的问题,但它可以给你一个 wtf 时刻。如果您的最终设置是从与后端不同的域为您的前端提供服务,那么您可能需要调整一些设置。

    接下来,启用 CORS 以允许将 cookie 包含在跨站点 http 请求中。这可能不是你的直接问题,但它会咬你下一个。

    # 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 only 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/ 第二个设置了一些不适用的设置并且它在反应中,但是视图设置仍然是一个好的开始。 您最终会想要合并身份验证,所以现在选择;会话或 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
    ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-03
      • 2016-11-26
      • 1970-01-01
      • 2021-10-08
      • 1970-01-01
      • 2022-09-27
      • 1970-01-01
      • 2020-01-16
      相关资源
      最近更新 更多