
跨站请求伪造(csrf)是一种常见的网络攻击,攻击者诱导用户在不知情的情况下向目标网站发送恶意请求。为了防范此类攻击,django 内置了强大的 csrf 保护机制。当处理 post、put、delete 等可能修改服务器状态的请求时,django 会要求请求中包含一个有效的 csrf 令牌。如果请求中缺少或包含无效的令牌,django 的 csrf 中间件将默认拒绝该请求,导致数据更新失败。
在模板中,通常会使用 {% csrf_token %} 标签来生成一个隐藏的输入字段,其中包含了 CSRF 令牌。对于 AJAX 请求,我们需要手动从 Cookie 或 DOM 中获取这个令牌,并将其作为请求头的一部分发送到服务器。
虽然 Django 提供了 @csrf_exempt 装饰器来豁免特定视图的 CSRF 检查,但除非有明确的理由(例如为公共 API 提供服务,并采用其他认证机制),否则不建议在处理敏感数据或修改操作的视图中使用它,因为它会削弱应用的安全性。正确的做法是始终在前端 AJAX 请求中包含 CSRF 令牌。
为了确保 AJAX POST 请求能够成功通过 Django 的 CSRF 验证,我们需要在 JavaScript 代码中获取 CSRF 令牌,并将其添加到请求头中。Django 的官方文档推荐通过解析 document.cookie 来获取 csrftoken。
以下是一个通用的 getCookie 函数,用于从浏览器 Cookie 中提取指定名称的 Cookie 值:
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}有了 getCookie 函数,我们就可以在 fetch API 请求中添加 X-CSRFToken 请求头。
// 确保 getCookie 函数在 postStatus 函数之前定义
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function postStatus(submissionId) {
const selectElement = document.getElementById(submissionId);
const status = selectElement.value;
const csrftoken = getCookie('csrftoken'); // 获取 CSRF 令牌
fetch(`/room/{{ room.join_code }}/bills/{{ bills.slug }}/status/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken // 在请求头中加入 CSRF 令牌
},
body: JSON.stringify({
"submission_id": submissionId, // 注意这里是 submission_id,与后端保持一致
"status": status
})
})
.then(response => {
if (!response.ok) {
// 处理非 2xx 响应,例如 403 Forbidden(CSRF 失败)
console.error('Network response was not ok: ', response.statusText);
return response.json().then(err => { throw new Error(JSON.stringify(err)); });
}
return response.json();
})
.then(data => {
console.log('Status updated successfully:', data);
// 可以根据需要添加成功提示或更新 UI
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
// 处理网络错误或解析错误
});
}请注意,在 body 中,我们使用了 "submission_id" 而不是 "submissionId",这是为了与后端 views.py 中 data["submission_id"] 的键名保持一致。
为了充分利用 Django 的 CSRF 保护机制,建议从视图函数中移除 @csrf_exempt 装饰器。当前端正确发送 X-CSRFToken 请求头时,Django 的中间件会自动验证该令牌。
import json
from django.http import JsonResponse
# from django.views.decorators.csrf import csrf_exempt # 移除此行
# 移除 @csrf_exempt 装饰器
def remark_proof_api(request, room_id, bills_slug):
if request.method == "POST":
try:
data = json.loads(request.body.decode("utf-8"))
submission_id = data.get("submission_id") # 使用 .get() 避免 KeyError
status = data.get("status")
if not submission_id or not status:
return JsonResponse({"success": False, "message": "Missing submission_id or status"}, status=400)
# 确保 submission_id 是整数
sub = Submission.objects.get(id=int(submission_id))
sub.status = status
sub.save()
return JsonResponse({"success": True, "message": "Status updated successfully"})
except Submission.DoesNotExist:
return JsonResponse({"success": False, "message": "Submission not found"}, status=404)
except json.JSONDecodeError:
return JsonResponse({"success": False, "message": "Invalid JSON"}, status=400)
except Exception as e:
# 捕获其他潜在错误
return JsonResponse({"success": False, "message": str(e)}, status=500)
return JsonResponse({"success": False, "message": "Invalid request method"}, status=405) # 处理非 POST 请求后端视图现在将依赖于 Django 默认的 CSRF 验证,如果前端未提供有效的 CSRF 令牌,请求将直接被中间件拒绝,并返回 403 Forbidden 错误。
以下是整合了上述修改后的前端 HTML (JavaScript 部分) 和后端 Python 代码示例:
view-bills-admin.html (JavaScript 部分)
<script>
// getCookie 函数用于从浏览器 Cookie 中获取 CSRF 令牌
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// postStatus 函数发送 AJAX 请求更新状态
function postStatus(submissionId) {
const selectElement = document.getElementById(submissionId);
const status = selectElement.value;
const csrftoken = getCookie('csrftoken'); // 获取 CSRF 令牌
fetch(`/room/{{ room.join_code }}/bills/{{ bills.slug }}/status/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken // 在请求头中加入 CSRF 令牌
},
body: JSON.stringify({
"submission_id": submissionId, // 确保键名与后端匹配
"status": status
})
})
.then(response => {
if (!response.ok) {
// 如果响应状态码不是 2xx,抛出错误
return response.json().then(err => { throw new Error(JSON.stringify(err)); });
}
return response.json();
})
.then(data => {
console.log('Status updated successfully:', data);
// 可以在此处添加 UI 反馈,例如显示成功消息
})
.catch(error => {
console.error('Error updating status:', error);
// 可以在此处添加 UI 反馈,例如显示错误消息
});
}
</script>views.py
import json
from django.http import JsonResponse
from .models import Submission # 假设 Submission 模型在当前应用的 models.py 中
# 移除 @csrf_exempt 装饰器,依赖 Django 默认的 CSRF 中间件
def remark_proof_api(request, room_id, bills_slug):
if request.method == "POST":
try:
data = json.loads(request.body.decode("utf-8"))
submission_id = data.get("submission_id")
status = data.get("status")
if not submission_id or not status:
return JsonResponse({"success": False, "message": "Missing submission_id or status"}, status=400)
sub = Submission.objects.get(id=int(submission_id))
sub.status = status
sub.save()
return JsonResponse({"success": True, "message": "Status updated successfully"})
except Submission.DoesNotExist:
return JsonResponse({"success": False, "message": "Submission not found"}, status=404)
except json.JSONDecodeError:
return JsonResponse({"success": False, "message": "Invalid JSON payload"}, status=400)
except Exception as e:
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"}, status=500)
return JsonResponse({"success": False, "message": "Invalid request method"}, status=405)正确处理 Django 中的 CSRF 令牌是构建安全、可靠的 AJAX 数据更新功能的基础。通过在前端 JavaScript 中获取并随请求发送 X-CSRFToken,并确保后端视图未被不恰当地豁免 CSRF 保护,我们可以有效地防范 CSRF 攻击,同时保证数据修改操作的顺畅进行。遵循这些最佳实践,将有助于你的 Django 应用更加健壮和安全。
以上就是Django AJAX POST 请求数据更新指南:正确处理 CSRF 令牌的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号