
在开发基于Django的Web应用时,我们经常需要通过前端JavaScript(如Fetch API或XMLHttpRequest)向后端发送异步请求来更新数据,例如更改某个状态或提交表单。一个常见的问题是,尽管前端代码看似正确发送了POST请求,并且后端视图也执行了数据保存操作(如instance.save()),但刷新页面后数据却回到了初始状态。这通常表现为数据未能成功持久化到数据库中。
例如,在预算管理应用中,尝试通过下拉菜单更改账单提交状态(Pending, Accepted, Rejected),但每次页面刷新后,状态都恢复为“Pending”。这表明后端可能并未真正接收到请求数据,或者在处理过程中被某个安全机制拦截。
Django默认开启了强大的跨站请求伪造(CSRF)保护机制。CSRF是一种恶意攻击,攻击者诱导用户在已登录状态下访问一个恶意网站,该网站向用户已登录的合法网站发送伪造的请求,利用用户的会话权限执行非法操作。
为了防范CSRF攻击,Django要求所有非GET、HEAD、OPTIONS、TRACE的请求(即会改变服务器状态的请求,如POST、PUT、DELETE)必须携带一个有效的CSRF令牌。这个令牌在用户首次访问页面时由Django生成并嵌入到HTML中(通常是隐藏的表单字段或Cookie中),并在后续的POST请求中被提交回服务器进行验证。如果请求中缺少CSRF令牌,或者令牌无效,Django的CSRF中间件会阻止该请求,导致数据无法到达视图函数,从而无法保存。
虽然可以使用@csrf_exempt装饰器来豁免某个视图的CSRF检查,但这会降低应用的安全性,通常不推荐在生产环境中使用,除非有非常明确的理由和额外的安全措施。最佳实践是始终在POST请求中包含CSRF令牌。
解决数据无法持久化问题的核心在于确保AJAX POST请求能够正确地携带CSRF令牌。
Django将CSRF令牌存储在一个名为csrftoken的Cookie中。在JavaScript中,我们需要编写一个辅助函数来从浏览器Cookie中提取这个令牌。
// getCookie函数用于从document.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;
}获取到CSRF令牌后,需要将其作为X-CSRFToken头部添加到Fetch请求的headers中。
{% 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="{{ submission.id }}" onblur="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>
// getCookie函数,用于从document.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();
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({
// 注意:这里将 "submissionId" 改为 "submission_id" 以匹配后端Python的命名习惯
"submission_id": submissionId,
"status": status
})
})
.then(response => {
// 检查响应状态码,例如200 OK
if (!response.ok) {
// 如果不是成功的响应,抛出错误
return response.json().then(errorData => {
throw new Error(`HTTP error! Status: ${response.status}, Message: ${JSON.stringify(errorData)}`);
});
}
return response.json(); // 解析JSON响应
})
.then(data => {
// 请求成功处理
if (data.success) {
console.log('Status updated successfully:', data);
// 可以在这里添加UI反馈,例如显示成功消息
} else {
console.error('Status update failed:', data);
// 可以在这里添加UI反馈,例如显示错误消息
}
})
.catch(error => {
// 请求失败或网络错误处理
console.error('Error updating status:', error);
// 可以在这里添加UI反馈,例如显示错误消息
});
}
// 页面加载后,根据当前submission.status设置select的选中项
document.addEventListener('DOMContentLoaded', function() {
{% for submission in submissions %}
const selectElement = document.getElementById('{{ submission.id }}');
if (selectElement) {
selectElement.value = '{{ submission.status }}';
}
{% endfor %}
});
</script>
{% endblock %}代码改进说明:
在后端,如果前端已经正确发送了CSRF令牌,那么您的Django视图就不需要使用@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 # 移除此行
from .models import Submission # 假设您的模型在这里
@require_POST # 确保只接受POST请求
# @csrf_exempt # 移除此装饰器,让CSRF中间件处理
def remark_proof_api(request, room_id, bills_slug):
# Django的CSRF中间件在请求到达这里之前已经验证了令牌
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)
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:
# 记录详细错误以便调试
print(f"Error updating submission status: {e}")
return JsonResponse({"success": False, "message": f"An error occurred: {str(e)}"}, status=500)后端视图改进说明:
urls.py中的配置保持不变,因为它正确地映射了URL路径到视图函数。
# urls.py
from django.urls import path
from . import views
urlpatterns = [
# ... 其他URL模式
path('room/<str:room_id>/bills/<str:bills_slug>/status/', views.remark_proof_api, name='remark-proof'),
]通过以上步骤,您已经成功地在Django AJAX POST请求中集成了CSRF保护,并解决了数据无法持久化的问题。
以上就是Django AJAX POST请求中的CSRF保护与数据持久化实践的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号