DRF使用PUT方法丢失CSRF令牌

时间:2019-05-21 20:35:57

标签: python django django-rest-framework django-rest-auth

我的项目中有一个常规的ModelViewSet,它可以很好地与GETPOST请求一起使用,但是对PUT却失败,并返回以下错误:

{
    "detail": "CSRF Failed: CSRF token missing or incorrect."
}

这是我的 urls.py

from django.urls            import path,re_path,include
from django.utils.text      import slugify,camel_case_to_spaces
from PaymentsManagerApp     import views, models
from rest_framework         import routers

APP_NAME = 'PaymentsManagerApp'
router = routers.DefaultRouter()

router.register(r'payments', views.PaymentViewSet)

payments_list = views.PaymentViewSet.as_view({
    'get':'list',
    'post':'create'
})

payment_detail = views.PaymentViewSet.as_view({
'get':'retrieve',
'put':'update',
'patch':'partial_update',
'delete':'destroy'

})

def urlpattern_from_route(route):
    if "regex" in route and route['regex']:
        path_method = re_path
    else:
        path_method = path
    return path_method(route['path'],route['view'].as_view(),name=route['name'] if "name" in route else None)

routes_views = list(map(urlpattern_from_route,routes))
route_services = [

payment_detail = views.PaymentViewSet.as_view({
    'get':'retrieve',
    'put':'update',
    'patch':'partial_update',
    'delete':'destroy'
})

route_services = [
    path('payments/', payments_list, name='rest_payments_list'),
    path('payments/<int:pk>/', payment_detail, name='rest_payment_detail'),
]

urlpatterns = routes_views + route_services

这是我的 views.py

import os
import json
from datetime                           import datetime, timedelta
from django.shortcuts                   import render
from PaymentsManagerApp                 import urls, models, serializers
from FrontEndApp                        import urls as Fronturls
from django.shortcuts                   import render,redirect
from django.contrib.auth.mixins         import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.views.generic               import View
from django.contrib.auth.models         import Permission
from GeneralApp.utils                   import get_catalogs
from django.contrib.staticfiles         import finders
from django.utils.text                  import slugify,camel_case_to_spaces
from rest_framework                     import viewsets, permissions
from rest_framework.response            import Response
from django_filters.rest_framework      import DjangoFilterBackend
from rest_framework.response            import Response
from rest_framework.filters             import OrderingFilter, SearchFilter
from django.db.models                   import Q

class PaymentViewSet(viewsets.ModelViewSet):
        exclude_from_schema = True

        permission_classes = (permissions.IsAuthenticated,)
        queryset = models.Payment.objects.all()
        serializer_class = serializers.PaymentSerializer
        filter_backends = (DjangoFilterBackend, SearchFilter, OrderingFilter,)
        search_fields = ('payment_type', 'creation_user__username', 'provider__name', 'invoice', 'payment_method_type', 'payment_document_number')
        filter_fields = ('id', 'payment_type', 'creation_user', 'provider', 'is_payment_requested', 'is_paid', 'payment_method_type')

当我将GET或POST发送到payments_manager/payments/时,它可以正常工作。另外,当我将GET发送到pyments_manager/payments/<int:pk>/时,效果很好。

问题是当我将PUT发送到payments_manager/payments/<int:pk>/时,因为得到以下信息:

enter image description here

我不知道为什么,但是DRF丢失了登录的用户信息(您可以看到登录标签,而不是用户名)。

编辑

这是我在 settings.py 中的REST_FRAMEWORK:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAuthenticated',),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ),
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 20,
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata'
}

编辑 我发现仅当我使用DRF默认界面(127.0.0.1:8000/es/payments_manager/payments/1/)从浏览器直接访问端点时,错误才会出现: enter image description here

我的PUT请求可以从我的javascript ajax完美运行。

1 个答案:

答案 0 :(得分:0)

为了使用会话身份验证并进行POST(有点怪异)/ PUT / PATCH / DELETE /等等,您必须传递标头

请参阅:https://github.com/django/django/blob/8b3f1c35dd848678225e8634d6880efeeab5e796/django/middleware/csrf.py#L306

我还为您创建了一个小测试:

https://gist.github.com/kingbuzzman/20dffbc34d22a899661ac3c065e3f747#file-django_rest_framework_session_vs_token-py-L209

  response = self.client.post('/session-login/', data={'username': 'user', 'password': 'pass'})
  self.assertEqual(302, response.status_code)
  self.assertIn('csrftoken', response.cookies)
  self.assertIn('sessionid', response.cookies)

  # Don't want to go through the trouble of having to get the CSRF from the login form
  self.client.handler.enforce_csrf_checks = True

  csrftoken = self.client.cookies.get('csrftoken').value

  # NOTE: The only reason this works it's because we're passing a header along with the request.
  response = self.client.patch('/payments/%s/' % (self.payment.id), content_type='application/json',
                               data=json.dumps({'is_paid': 'Y'}), HTTP_X_CSRFTOKEN=csrftoken)
  self.assertEqual(200, response.status_code)
  self.assertEqual('Y', response.json()['is_paid'])

  # NOTE: The reason this DOES NOT works it's because we're NOT passing a header along with the request.
  response = self.client.patch('/payments/%s/' % (self.payment.id), content_type='application/json',
                               data=json.dumps({'is_paid': 'N'}))
  self.assertEqual(403, response.status_code)

编辑。

添加了一个用户,登录后,导航到localhost/payments/并添加了一条记录,然后转到记录localhost/payments/1/并对其进行了更新(PUT)。一切正常。请添加您的django / drf版本。

enter image description here