
在Django中,为了防止跨站请求伪造(CSRF)攻击,框架对所有非幂等HTTP方法(如POST、PUT、DELETE)强制执行CSRF验证。这意味着,当你从前端通过AJAX发送POST请求到Django后端时,请求中必须包含一个有效的CSRF令牌。如果缺少这个令牌,或者令牌不匹配,Django会拒绝该请求,导致数据无法更新。
尽管在某些情况下,开发者可能会在视图函数上使用@csrf_exempt装饰器来暂时禁用CSRF保护,但这并非最佳实践,因为它会降低应用的安全性。理想情况下,即使是AJAX请求,也应该正确地包含并验证CSRF令牌。本教程将指导你如何在前端正确地获取并发送CSRF令牌,从而在不牺牲安全性的前提下实现数据更新。
为了在AJAX请求中包含CSRF令牌,我们需要执行以下步骤:
以下是获取CSRF令牌的JavaScript函数以及如何在fetch API中使用的示例:
{% extends "base/room_home.html" %}
{% block content %}
<div class="container d-flex align-items-center justify-content-center" style="min-height: 100vh;">
<div class="text-center">
<h1>{{ room_bills.title }}</h1>
<p>Due: {{ room_bills.due }}</p>
<div class="col-3">
<h3>Paid Members: </h3>
<ul>
{% for submission in submissions %}
<li>
<a href="proof/{{ submission.user.id }}" target="_blank">{{ submission.user.username }} {{ submission.text }}</a>
<form>
<label for="status">Status:</label>
<select name="status" id="status-{{ submission.id }}" onchange="postStatus('{{ submission.id }}')">
<option value="P" {% if submission.status == 'P' %}selected{% endif %}>Pending</option>
<option value="A" {% if submission.status == 'A' %}selected{% endif %}>Accepted</option>
<option value="R" {% if submission.status == 'R' %}selected{% endif %}>Rejected</option>
</select>
</form>
</li>
{% endfor %}
</ul>
</div>
<div class="col-3">
<h3>Did not pay members: </h3>
<ul>
{% for user in did_not_submit %}
<li>{{ user.username }}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<script>
// 辅助函数:从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();
// 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;
}
// 更新状态的AJAX请求函数
function postStatus(submissionId) {
const selectElement = document.getElementById(`status-${submissionId}`); // 使用更具体的ID
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({
"submissionId": submissionId, // 保持与后端期望的键名一致
"status": status
})
})
.then(response => {
if (!response.ok) {
// 处理HTTP错误状态,例如403 Forbidden (CSRF token missing/invalid)
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
console.log('Status updated successfully:', data);
// 可以在此处添加用户反馈,例如显示一个成功消息
} else {
console.error('Status update failed:', data);
// 可以在此处添加用户反馈,例如显示一个错误消息
}
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
// 可以在此处添加网络错误或解析错误的用户反馈
});
}
// 初始设置select元素的选中状态
document.addEventListener('DOMContentLoaded', function() {
{% for submission in submissions %}
const selectElement = document.getElementById(`status-{{ submission.id }}`);
if (selectElement) {
selectElement.value = "{{ submission.status }}";
}
{% endfor %}
});
</script>
{% endblock %}代码改进点说明:
在后端,一旦前端正确地发送了CSRF令牌,Django的CSRF中间件会自动验证它。因此,你的视图函数可以保持相对简洁。最重要的是,如果你之前为了调试或其他原因使用了@csrf_exempt,现在应该考虑移除它,让Django的CSRF保护机制发挥作用。
import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST # 推荐使用
# from django.views.decorators.csrf import csrf_exempt # 如果前端已发送CSRF,建议移除此行
# 推荐使用 @require_POST 确保只响应POST请求
# 如果前端已发送CSRF令牌,请移除 @csrf_exempt
# @csrf_exempt
@require_POST
def remark_proof_api(request, room_id, bills_slug):
try:
data = json.loads(request.body.decode("utf-8"))
submission_id = data.get("submissionId") # 使用.get()方法更安全
status = data.get("status")
if not submission_id or not status:
return JsonResponse({"success": False, "message": "Missing submissionId or status"}, status=400)
# 确保 submission_id 是整数
try:
sub = Submission.objects.get(id=int(submission_id))
except Submission.DoesNotExist:
return JsonResponse({"success": False, "message": "Submission not found"}, status=404)
except ValueError:
return JsonResponse({"success": False, "message": "Invalid submissionId format"}, status=400)
# 验证状态值是否有效(根据你的模型定义)
valid_statuses = ['P', 'A', 'R'] # 假设你的Submission模型status字段有这些选项
if status not in valid_statuses:
return JsonResponse({"success": False, "message": "Invalid status value"}, status=400)
sub.status = status
sub.save()
return JsonResponse({"success": True, "message": "Status updated successfully"})
except json.JSONDecodeError:
return JsonResponse({"success": False, "message": "Invalid JSON format"}, status=400)
except Exception as e:
# 捕获其他未知错误
return JsonResponse({"success": False, "message": f"An unexpected error occurred: {str(e)}"}, status=500)
后端视图改进点说明:
URL配置保持不变,它正确地将前端请求路由到后端视图:
from django.urls import path
from . import views # 假设views.py在当前应用目录下
urlpatterns = [
# ... 其他URL模式
path('room/<str:room_id>/bills/<str:bills_slug>/status/', views.remark_proof_api, name='remark-proof'),
# ...
]通过以上步骤,你的Django预算网站的账单状态更新功能将能够正常工作并持久化。关键在于理解并正确实现Django的CSRF保护机制。
遵循这些最佳实践,你的Django应用将更加安全、稳定和用户友好。
以上就是Django AJAX状态更新:解决CSRF令牌缺失问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号