Django 如何防止跨站请求伪造(CSRF)攻击?
跨站请求伪造(Cross-Site Request Forgery,简称 CSRF)是一种常见的 Web 安全漏洞,攻击者通过诱导用户在已登录的网站上执行非预期操作。Django 框架内置了强大的 CSRF 保护机制,本文将详细介绍其工作原理和实现方式。
CSRF 攻击原理
CSRF 攻击利用用户的已认证状态,在用户不知情的情况下向目标网站发送恶意请求。例如:
用户登录银行网站后保持会话
访问恶意网站,该网站包含向银行网站转账的请求
浏览器自动携带用户的认证 cookie 发送请求
银行网站误认为是用户的合法操作
Django CSRF 保护机制
1. CSRF 令牌验证
Django 通过在每个 POST 请求中验证 CSRF 令牌来防止攻击。基本流程如下:
服务器生成唯一的 CSRF 令牌并存储在 session 中
令牌通过模板标签渲染到表单中
客户端提交表单时包含该令牌
服务器验证令牌的有效性
2. 中间件实现
Django 的 CSRF 保护主要通过 CsrfViewMiddleware 中间件实现。该中间件会:
检查请求方法是否需要 CSRF 保护(默认:POST、PUT、PATCH、DELETE)
从请求中提取 CSRF 令牌
验证令牌的有效性
拒绝无效的请求并返回 403 错误
具体实现步骤
1. 启用 CSRF 中间件
确保在 settings.py 中启用了 CSRF 中间件:
MIDDLEWARE = [ # ... 'django.middleware.csrf.CsrfViewMiddleware', # ... ]
2. 在模板中使用 CSRF 令牌
对于 HTML 表单,使用 {% csrf_token %} 模板标签:
<form method="post">
{% csrf_token %}
<!-- 其他表单字段 -->
<input type="text" name="username">
<input type="submit" value="Submit">
</form>该标签会在表单中生成一个隐藏的 input 字段,包含 CSRF 令牌:
<input type="hidden" name="csrfmiddlewaretoken" value="Kx...Xy">
3. AJAX 请求的 CSRF 保护
对于 AJAX 请求,需要在请求头中包含 CSRF 令牌。有几种方式可以实现:
方法一:从 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;
}
const csrftoken = getCookie('csrftoken');
// 使用 fetch API 发送请求
fetch('/api/data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({key: 'value'})
});方法二:使用 jQuery
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});4. 装饰器保护视图
可以使用 csrf_protect 装饰器强制对特定视图进行 CSRF 保护:
from django.views.decorators.csrf import csrf_protect from django.shortcuts import render @csrf_protect def my_view(request): if request.method == 'POST': # 处理 POST 请求 return render(request, 'success.html') return render(request, 'form.html')
或者使用 csrf_exempt 装饰器排除特定视图的 CSRF 保护(谨慎使用):
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def api_endpoint(request):
# 这个视图不受 CSRF 保护
return JsonResponse({'status': 'ok'})常见问题与解决方案
1. 403 Forbidden 错误
出现 403 错误通常是因为 CSRF 令牌验证失败。可能的原因包括:
未在表单中包含
{% csrf_token %}AJAX 请求未设置 CSRF 令牌
会话过期导致令牌失效
跨域请求未正确处理
2. 跨域请求处理
对于跨域请求,需要确保:
正确配置 CORS 策略
在前端请求中包含凭据(credentials)
服务器端设置适当的 CORS 头部
3. 测试环境中的 CSRF
在测试环境中,可以使用以下方法绕过 CSRF 保护:
from django.test import TestCase
class MyTest(TestCase):
def test_my_view(self):
response = self.client.post(
'/my-url/',
data={'key': 'value'},
HTTP_X_CSRFTOKEN=self.client.cookies['csrftoken'].value
)
self.assertEqual(response.status_code, 200)最佳实践
始终在生产环境中启用 CSRF 保护
对所有修改数据的请求使用 POST、PUT、PATCH、DELETE 方法
不要在 GET 请求中执行敏感操作
定期更新 Django 版本以获取最新的安全修复
对用户输入进行严格的验证和过滤
使用 HTTPS 加密传输数据
总结
Django 的 CSRF 保护机制通过令牌验证有效防止了跨站请求伪造攻击。开发者只需遵循简单的配置步骤,即可为应用提供强大的安全保障。理解其工作原理并在实际项目中正确实施,是构建安全 Web 应用的重要环节。