一条路由的 CSRF 令牌丢失或不正确,但另一条路由则不然



在React/Django上工作,遇到了一个我无法解决的问题。

在前端,有以下JS将数据发送到Django API。在这种情况下,rejectedDocuemts()会向后端发送一系列filenames,以便最终可以创建电子邮件并将其发送给管理员进行审核。与批准的扩展名列表不匹配的文件将在此列表中。这是发生错误的地方。

submitDocuments是提交符合批准的文件扩展名列表的文件的位置。整个files对象被发送到服务器进行保存。这个工作得很好。

export function rejectedDocuments(filenames, id) {
return function (dispatch) {
axios.post(
`${URL}/api/${(id)}/documents/upload/error`,
filenames,
{ 
headers: {
'content-type': 'text/plain',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
}
}  
)
}
}
// Creates the error state to be used in components
export function submitDocuments(files, id) {
return function (dispatch) {
const uploaders = _.map(files, f => {
const formData = new FormData();
formData.append('file', f);
return axios.post(
`${URL}/api/${(id)}/documents/upload/success`,
formData,
{ 
headers: { 
'content-type': 'multipart/form-data',
'Authorization': 'JWT ' +  sessionStorage.getItem('token')
}
}          
)
});
axios.all(uploaders)
.then(response => {
dispatch({ 
type: SUBMIT,
payload: response[0].status
});
})     
}
}

rejectedDocuments类是发生错误(Forbidden (CSRF token missing or incorrect.): /api/84/documents/upload/error(的地方。检查网络标头时,它们都包含Cookie: csrftoken=xyz。它们都具有相同的CSRF令牌。

所以不确定发生了什么或如何绕过它。submitDocuments工作得很好,它们彼此之间并没有太大的不同,除了一个发送文件对象(submitDocuments(和另一个发送字符串数组(rejectedDocuments(。

无论如何,这里是 Django 后端:

# documents/urls.py
from django.conf.urls import url
from .views import (
GetDocumentsAPIView, 
RejectedDocumentsView,
SaveDocumentAPIView
)
app_name = 'Documents'
urlpatterns = [
url(r'^documents/upload/success', SaveDocumentAPIView.as_view(), name='upload'),
url(r'^documents/upload/error', RejectedDocumentsView.as_view(), name='rejected'),
url(r'^documents/', GetDocumentsAPIView.as_view(), name='documents')
]

# documents/views.py
import datetime
from django.contrib.auth import get_user_model
from django.db.models import Max
from django.views import View
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from rest_framework.views import APIView
from .serializers import GetDocumentsSerializer, SaveDocumentSerializer
from .models import Uploads
User = get_user_model()
# Create your views here.
class GetDocumentsAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
data = {'id': request.user.id}
if kwargs:
data['sur_id'] = kwargs.get('sur_id')
serializer = GetDocumentsSerializer(data=data)
if serializer.is_valid(raise_exception=True):
return Response(serializer.data, status=HTTP_200_OK)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
class RejectedDocumentsView(View):
def post(request):
print(request)
class SaveDocumentAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
max_id = Uploads.objects.all().aggregate(Max('id'))
if max_id['id__max'] == None:
max_id = 1
else:    
max_id = max_id['id__max'] + 1
data = {
'user_id': request.user.id,
'sur_id': kwargs.get('sur_id'),
'co': User.objects.get(id=request.user.id).co,
'date_uploaded': datetime.datetime.now(),
'size': request.FILES['file'].size
}
filename = str(data['co']) + '_' + str(data['sur_id']) + '_' + str(max_id) + '_' + request.FILES['file'].name
data['doc_path'] = filename
self.save_file(request.FILES['file'], filename)
serializer = SaveDocumentSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=HTTP_200_OK)
return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)
# Handling the document
def save_file(self, file, filename):
with open('flu/' + filename, 'wb+') as destination:
for chunk in file.chunks():
destination.write(chunk)

不发布序列化,因为RejectedDocumentsView(View)不需要序列化,并且错误似乎在进入视图之前就发生了。

我重新评估了我的方法,发现它是有缺陷的。数据都是通过同一个<form>提交的,所以我想到它应该都通过同一个FormData()提交。

我摆脱了rejectedFiles函数,最终将submitDocuments函数重写为:

export function submitDocuments(accepetedFiles, rejectedFiles, id) {
return function (dispatch) {
var formData = new FormData();
// handle the accepetedfile and append to formData
_.map(accepetedFiles, f => {
formData.append('file', f);
});
// handle the rejectedfiles and append to formData
_.map(rejectedFiles, r => {
formData.append('rejected', r)
})
// Send the formDat to the server
axios.post(
`${URL}/api/${(id)}/documents/upload`,
formData,
{
headers: {
'content-type': 'multipart/form-data',
'Authorization': 'JWT ' + sessionStorage.getItem('token')
}
}
)
// handling of the server response, should enable the modal to render
.then(response => {
dispatch({ 
type: SUBMIT,
payload: response.status
});
})  
}
}

这将创建包含所有文件和文件名数组的FormData(),这些文件和文件名的扩展名是不允许的。

在后端,我在urls.py中删除了以下内容:

url(r'^documents/upload/error', RejectedDocumentsView.as_view(), name='rejected'),

最后是这样的:

from django.conf.urls import url
from . import views
from .views import (
GetDocumentsAPIView, 
SaveDocumentAPIView
)
app_name = 'Documents'
urlpatterns = [
url(r'^documents/upload', SaveDocumentAPIView.as_view(), name='upload'),
url(r'^documents/', GetDocumentsAPIView.as_view(), name='documents')
]

最后,我更新了views.py以处理随request.POST传入的数组:

class SaveDocumentAPIView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request, *args, **kwargs):
# this for handlign the files we DON't want
# files are not actually just sent to the server
# just the files names so an email can be sent to the admin
# to review the file types
user_obj = User.objects.filter(id=request.user.id).first()
if len(request.POST.getlist('rejected')) >= 1:
send_mail(
'Invalid File Extension',
'The user %s %s with email %s has submitted the following files with invalid extensions: nn %s' % 
(user_obj.first_name, user_obj.last_name, user_obj.email, request.POST.getlist('rejected')),
'The Company <no-reply@company.com>',
['admin@company.com']
)
# this is for handling the files we do want
# it writes the files to disk and writes them to the database
for f in request.FILES.getlist('file'):
max_id = Uploads.objects.all().aggregate(Max('id'))
if max_id['id__max'] == None:
max_id = 1
else:    
max_id = max_id['id__max'] + 1
data = {
'user_id': request.user.id,
'sur_id': kwargs.get('sur_id'),
'co': User.objects.get(id=request.user.id).co,
'date_uploaded': datetime.datetime.now(),
'size': f.size
}
filename = str(data['co']) + '_' + 
str(data['sur_id']) + '_' + 
str(max_id) + '_' + 
f.name
data['doc_path'] = filename
self.save_file(f, filename)
serializer = SaveDocumentSerializer(data=data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(status=HTTP_200_OK)
# Handling the document
def save_file(self, file, filename):
with open('fileupload/' + filename, 'wb+') as destination:
for chunk in file.chunks():
destination.write(chunk)

虽然草率,但它做了我需要它做的事情。

最新更新