'# django使用ajax PUT/DELETE方法请求报错解决:“forbidden (CSRF token missing or incorrect.)”
一、背景与问题
在Django中,CSRF(Cross-Site Request Forgery)保护机制是默认启用的。该机制通过在模板中插入{% csrf_token %}标签生成一个随机token,并在请求头中通过X-CSRFToken字段传递。对于常规的HTML表单提交,Django会自动处理这个过程,但AJAX请求需要开发者手动处理。
当使用AJAX发送PUT或DELETE请求时,常见错误提示为:"forbidden (CSRF token missing or incorrect.)"。这个问题的根本原因在于:Django的CSRF中间件认为当前请求缺少或错误的CSRF token。
二、基本原理
Django的CSRF保护机制通过以下步骤实现:
- 服务器生成一个随机token并存储在session中
- 在模板中插入
<input type="hidden" name="csrfmiddlewaretoken" value="...">字段 - 客户端提交表单时,自动携带该token
- 服务器验证token的合法性
对于AJAX请求,需要手动实现:
- 从模板中获取token
- 在AJAX请求头中添加
X-CSRFToken字段 - 服务器验证该字段的合法性
三、环境准备
确保环境满足以下条件:
- Django 3.2+(支持CSRF_COOKIE_DOMAIN)
- Python 3.8+
- 前端使用JavaScript(如jQuery或Axios)
四、核心实现
1. 获取CSRF Token
在模板中使用{% csrf_token %}标签会生成一个包含token的<input>字段,可以通过JavaScript获取:
<!-- templates/index.html -->
<input type="hidden" id="csrf-token" value="{{ csrf_token }}">
// 获取CSRF Token
const csrfToken = document.getElementById('csrf-token').value;
2. 在AJAX请求中携带token
使用jQuery时,可以这样设置请求头:
$.ajax({
url: '/api/delete/',
type: 'DELETE',
headers: {
'X-CSRFToken': csrfToken
},
success: function(response) {
console.log('删除成功:', response);
}
});
使用Axios时需要显式设置:
axios.delete('/api/delete/', {
headers: {
'X-CSRFToken': csrfToken
}
}).then(response => {
console.log('删除成功:', response.data);
});
3. 跨域请求时的处理
当使用CORS时,需要配置Django的CORS_ALLOWED_ORIGINS,并确保CSRF token能正确传递:
# settings.py
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://another-example.com"
]
五、完整案例
场景:删除评论功能
后端代码(views.py)
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json
@csrf_exempt # 临时禁用CSRF保护用于演示
def delete_comment(request):
if request.method == 'DELETE':
try:
data = json.loads(request.body)
comment_id = data.get('id')
# 实际业务逻辑:从数据库删除评论
return JsonResponse({'status': 'success', 'message': f'Comment {comment_id} deleted'})
except Exception as e:
return JsonResponse({'status': 'error', 'message': str(e)}, status=400)
return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=405)
前端代码(index.html)
<!-- templates/index.html -->
<input type="hidden" id="csrf-token" value="{{ csrf_token }}">
<button id="deleteBtn">Delete Comment</button>
<script>
document.getElementById('deleteBtn').addEventListener('click', function() {
const csrfToken = document.getElementById('csrf-token').value;
fetch('/api/delete/', {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: 123 })
})
.then(response => {
if (!response.ok) throw new Error('Network response was not ok');
return response.json();
})
.then(data => {
console.log('Success:', data);
})
.catch(error => {
console.error('Error:', error);
});
});
</script>
六、源码解析
Django的CSRF中间件核心代码位于django/middleware/csrf.py,关键逻辑如下:
class CsrfViewMiddleware:
def process_request(self, request):
# 处理GET请求,设置模板上下文变量
if request.method == 'GET':
request.csrf_processing_done = True
return
# 处理其他请求
if request.method in ('POST', 'PUT', 'DELETE'):
if request.META.get('HTTP_X_CSRFTOKEN'):
# 验证X-CSRFToken头
if not csrf_token(request):
raise exceptions.PermissionDenied("CSRF token missing or incorrect.")
else:
# 验证表单中的csrfmiddlewaretoken
if not request.POST.get('csrfmiddlewaretoken'):
raise exceptions.PermissionDenied("CSRF token missing or incorrect.")
七、进阶使用
1. 自定义CSRF验证
在需要特殊处理的场景下,可以自定义验证逻辑:
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponseForbidden
@csrf_exempt
def custom_csrf_view(request):
if request.method == 'DELETE':
# 自定义验证逻辑
if not request.headers.get('X-CSRFToken'):
return HttpResponseForbidden("CSRF token missing")
# 验证逻辑...
2. 跨域请求的解决方案
使用Django CORS Headers扩展:
# settings.py
INSTALLED_APPS += ['corsheaders']
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
...
]
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://another-example.com"
]
八、性能与工程实践
1. 性能优化
- 缓存CSRF token:对于频繁访问的页面,可以缓存token以减少生成开销
- 使用Django的内置CSRF验证:相比自定义实现,内置验证经过充分测试
- 避免不必要的验证:对于GET请求不需要CSRF验证
2. 安全风险
- CSRF token泄露:若将token暴露给第三方,可能导致XSS攻击
- 跨域请求漏洞:未正确配置CORS可能导致CSRF攻击
- 会话固定攻击:如果token生成算法不够随机,可能被预测
九、常见问题与踩坑
1. 常见错误
| 错误场景 | 原因 | 解决方案 |
|---|
| 未携带token | 忘记在AJAX请求中添加X-CSRFToken | 在请求头中添加 X-CSRFToken: ${csrfToken} |
| token过期 | 会话超时后未重新获取token | 在每次请求前重新获取token |
| 跨域请求失败 | 未正确配置CORS | 使用corsheaders扩展并配置CORS_ALLOWED_ORIGINS |
| token验证失败 | 前端未正确获取token | 检查模板中是否包含{% csrf_token %}标签 |
2. 常见错误示例
错误代码:
fetch('/api/delete/', {
method: 'DELETE',
body: JSON.stringify({ id: 123 })
});
错误原因: 未携带CSRF token头
改进代码:
fetch('/api/delete/', {
method: 'DELETE',
headers: {
'X-CSRFToken': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({ id: 123 })
});
十、最佳实践
1. 推荐方案
- 所有AJAX请求都应携带CSRF token
- 使用Django的内置CSRF保护机制
- 对于API接口,建议同时使用JWT等其他认证机制
- 跨域请求时务必配置CORS
2. 不推荐方案
- 临时禁用CSRF保护(仅用于测试)
- 将CSRF token暴露给第三方
- 在非敏感操作中使用CSRF保护
十一、总结
Django的CSRF保护机制是保障Web应用安全的重要防线。在使用AJAX发送PUT/DELETE请求时,需要特别注意CSRF token的获取和传递。本文深入分析了CSRF保护的工作原理,提供了完整的代码示例和解决方案,同时探讨了性能优化、安全风险和常见错误。在实际开发中,应根据具体场景选择合适的保护方案,既要保证安全性,也要兼顾开发效率。