Django 中的 CSRF 防护机制详解
CSRF 攻击的基本概念
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的安全漏洞,攻击者利用用户已登录的身份,在其不知情的情况下发送恶意请求。这种攻击依赖于浏览器自动携带目标站点 Cookie 的特性,从而让服务器误认为请求来自合法用户。
攻击流程分析
- 用户登录网站 A,服务器验证通过后设置会话 Cookie,浏览器保存该凭证。
- 用户在未退出网站 A 的情况下访问恶意网站 B。
- 网站 B 包含一个指向网站 A 的隐藏表单或 AJAX 请求,例如发起转账、修改密码等操作。
- 当请求被触发时,浏览器自动附加网站 A 的 Cookie,使服务器认为这是合法操作。
- 服务器根据当前会话权限执行敏感操作,导致数据被篡改或账户被盗用。
防御原理:Token 校验机制
为防止此类攻击,Django 使用基于随机 Token 的防护策略:
- 服务器生成一个难以预测的加密字符串(CSRF Token),并将其嵌入响应页面中(如隐藏字段)。
- 同时将该 Token 存储在用户的 Cookie 中。
- 当用户提交 POST 请求时,必须同时提供表单中的 Token 和 Cookie 中的 Token。
- 服务器比对两者是否一致,只有匹配才允许处理请求。
由于同源策略限制,外部站点无法读取目标站点的 Cookie 或获取动态生成的 Token,因此无法构造有效的请求。
Django 内置的 CSRF 中间件
Django 默认启用了 django.middleware.csrf.CsrfViewMiddleware,位于 MIDDLEWARE 配置项中。该中间件自动为所有适用的视图添加保护逻辑。
全局启用与局部控制
全局防护由中间件统一管理,开发者也可通过装饰器对特定视图进行细粒度控制:
from django.views.decorators.csrf import csrf_exempt, csrf_protect
- csrf_exempt:豁免某个视图的 CSRF 检查,适用于无需身份验证的接口(如第三方回调)。
- csrf_protect:强制对该视图启用检查,即使全局未开启中间件也生效。
函数视图中的使用方式
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
elif request.method == 'POST':
# 处理登录逻辑
return HttpResponse('登录成功')
类视图中的配置方法
对于基于类的视图(Class-Based View),需结合 method_decorator 使用:
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class LoginView(TemplateView):
template_name = 'login.html'
def post(self, request):
return HttpResponse('支持 POST 请求')
或者直接在 URL 路由中装饰 as_view() 方法:
from django.urls import path
from django.views.decorators.csrf import csrf_exempt
urlpatterns = [
path('login/', csrf_exempt(LoginView.as_view()), name='login'),
]
模板中的 Token 插入
在 HTML 表单中,必须包含 {% csrf_token %} 标签以生成隐藏输入框:
<form method="post">
{% csrf_token %}
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">登录</button>
</form>
渲染后会自动生成类似以下结构:
<input type="hidden" name="csrfmiddlewaretoken" value="ABC123xyz...">
AJAX 请求中的 CSRF 处理
使用 JavaScript 发送非表单请求(如 JSON 数据)时,需手动从 Cookie 提取 Token 并设置请求头:
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');
$.ajax({
url: '/api/v1/orders/',
type: 'POST',
data: JSON.stringify(orderData),
contentType: 'application/json',
headers: {
'X-CSRFToken': csrftoken
},
success: function(result) {
console.log('订单创建成功');
}
});
Django 会检查 X-CSRFToken 请求头,并与 Cookie 中的值进行对比验证。
常见注意事项
- 静态文件和公开 API 可考虑豁免 CSRF,但需确保不涉及会话状态更改。
- 测试环境中禁用 CSRF 应谨慎操作,避免养成忽略安全的习惯。
- 前后端分离项目建议使用其他认证机制(如 JWT),避免依赖 Session 和 CSRF。