Django中AJAX请求CSRF Token缺失问题排查与解决
问题解决与优化方案
核心错误修复
你遇到的CSRF token missing报错,根源就是PUT请求代码里把fetch配置的headers写成了大写的Headers——fetch API的配置对象中,请求头的属性名是小写的headers,大写属性会被忽略,导致CSRF Token根本没被携带到请求里,所以Django判定Token缺失。
修正后的PUT请求代码:
fetch(`/edit/login=${id}`, { // 修正原URL多余的右大括号 method: "PUT", headers: { // 改成小写headers "X-CSRFToken": getCookie("csrftoken"), "Content-Type": "application/json", }, mode: "same-origin", body: JSON.stringify(body), })
关于Token不同却报缺失的疑问
每个表单的CSRF Token不同是正常的——Django会为每个请求(或会话周期)生成独立的Token,这是防止CSRF攻击的安全设计。你之前的报错是Token缺失而非无效,正是因为大小写错误导致请求根本没带上Token,Django连验证的机会都没有,自然报缺失。
更简便的实现方式
1. 直接复用表单里的CSRF Token
既然你通过GET请求获取了Django渲染的表单,表单里已经包含{% csrf_token %}生成的隐藏字段,直接从表单里提取更省事,不用写getCookie函数:
// 假设edit_form是你创建的表单元素 const csrfToken = edit_form.querySelector('[name="csrfmiddlewaretoken"]').value; fetch(`/edit/login=${id}`, { method: "PUT", headers: { "X-CSRFToken": csrfToken, "Content-Type": "application/json", }, mode: "same-origin", body: JSON.stringify(body), })
2. 封装通用的带CSRF的fetch函数
避免每次写请求都重复处理CSRF,封装一个工具函数:
function csrfFetch(url, options = {}) { const csrfToken = document.querySelector('[name="csrfmiddlewaretoken"]')?.value || getCookie("csrftoken"); options.headers = { ...options.headers, "X-CSRFToken": csrfToken, "Content-Type": "application/json", }; options.mode = "same-origin"; return fetch(url, options); } // 使用示例 csrfFetch(`/edit/login=${id}`, { method: "PUT", body: JSON.stringify(body), })
3. 用POST模拟PUT请求(Django原生支持)
Django支持通过POST请求携带_method=PUT参数来模拟PUT请求,这样可以直接利用表单的提交逻辑,不用手动处理AJAX的headers:
// 提交表单时改成POST,携带_method参数 const formData = new FormData(edit_form); formData.append("_method", "PUT"); fetch(`/edit/login=${id}`, { method: "POST", body: formData, mode: "same-origin", })
这种方式下,Django会把请求识别为PUT,同时自动处理CSRF验证(因为表单里已经有CSRF Token字段)。
Views.py的补充修正
你的views.py还有几个小问题需要修复:
from django.http import JsonResponse, Http404 from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required # 用装饰器限制请求方法,更清晰 @login_required @require_http_methods(["GET", "PUT"]) def edit_login(request, id): try: login = Entry.objects.get(id=id, owner=request.user) # 提前过滤用户权限,避免后续判断 except Entry.DoesNotExist: raise Http404("Entry not found") if request.method == "GET": initial = { "title": login.title, "username": login.username, "password": login.password, "note": login.note, "folder": login.folder, "protected": login.protected, "favorite": login.favorite, } form = LoginForm(initial=initial) # 修复原代码变量名错误 return render(request, 'vault/new_item.html', {"entry_form": form, 'uri_field': uri}) elif request.method == "PUT": data = json.loads(request.body) # 这里添加实际的更新逻辑,比如: # login.title = data.get("title", login.title) # login.save() print("test") return JsonResponse({"message": "Successfully edited"}, status=200 )
- 用
@require_http_methods限制请求方法,更规范 - 提前通过
Entry.objects.get(id=id, owner=request.user)过滤用户权限,避免获取不属于当前用户的条目 - 修复了
initial=edit的变量名错误,改成initial=initial - 添加了
try-except处理条目不存在的情况,返回404
内容的提问来源于stack exchange,提问作者Divroth
相关产品推荐
相关产品推荐

