Django 如何防止跨站请求伪造(CSRF)攻击?

Django 如何防止跨站请求伪造(CSRF)攻击?
最新回答
清雅幽兰

2021-01-29 09:40:23

Django 通过基于令牌的验证机制和浏览器特性结合的方式防止跨站请求伪造(CSRF)攻击,其核心原理是验证请求中两个令牌的匹配性,并利用浏览器跨域限制提供双重保护。 以下是具体实现方式及关键细节:

一、Django CSRF 防护的核心机制:双令牌验证

Django 的 CSRF 防护通过检查请求中的两个令牌是否匹配来确认请求合法性,具体流程如下:

  • csrfmiddlewaretoken(动态令牌)

    存储在 HTML 表单的隐藏字段或 AJAX 请求的请求头中(如 X-CSRFToken)。

    每次页面加载时由 Django 动态生成并更新,确保每次请求的令牌唯一。

    攻击者无法提前获取或预测该令牌,因其与用户会话和页面加载时间绑定。

  • csrftoken(静态令牌)

    存储在用户浏览器的 Cookie 中(默认名称为 csrftoken),通过 HTTP 响应的 Set-Cookie 头设置。

    令牌在用户会话期间保持不变,但仅用于与 csrfmiddlewaretoken 对比验证。

    攻击者即使窃取 Cookie,也无法修改其值(因 Cookie 的 HttpOnly 和 Secure 属性限制)。

验证流程

  1. 用户访问受保护的页面时,Django 在响应中同时设置 csrftoken Cookie 和嵌入 csrfmiddlewaretoken 到表单或页面。
  2. 用户提交请求时,Django 从请求头(如 Cookie)中提取 csrftoken,从请求体(如表单)或请求头(如 X-CSRFToken)中提取 csrfmiddlewaretoken。
  3. 若两者匹配,请求被视为合法;否则拒绝请求并返回 403 Forbidden。
二、Django CSRF 防护的有效性原理
  • 动态令牌的不可预测性csrfmiddlewaretoken 每次生成均不同,攻击者无法通过单一请求复用令牌,即使窃取当前令牌,下次请求时已失效。

  • 静态令牌的不可修改性csrftoken 由 Django 服务器控制,攻击者无法通过篡改 Cookie 伪造合法令牌,因验证需动态令牌与静态令牌严格匹配。

  • 跨域请求的限制

    浏览器同源策略阻止攻击者直接读取受害者的 Cookie 或 DOM 内容(如隐藏字段)。

    即使攻击者诱导用户点击链接或提交表单,浏览器仍会携带受害者的 Cookie 发起请求,但 Django 的双令牌验证会因缺少有效的 csrfmiddlewaretoken 而拒绝请求。

三、Django 中启用 CSRF 防护的配置
  1. 中间件配置确保 django.middleware.csrf.CsrfViewMiddleware 在 settings.py 的 MIDDLEWARE 列表中启用(默认已激活):

    MIDDLEWARE = [ # ... 'django.middleware.csrf.CsrfViewMiddleware', # ...]
  2. 表单与 AJAX 请求处理

    表单请求:Django 的 {% csrf_token %} 模板标签会自动生成隐藏字段,包含 csrfmiddlewaretoken。

    AJAX 请求:需手动从 Cookie 或 DOM 中获取 csrftoken 并设置为请求头 X-CSRFToken: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/data/', { method: 'POST', headers: { 'X-CSRFToken': csrftoken, 'Content-Type': 'application/json', }, body: JSON.stringify({key: 'value'}),});

  3. 豁免特定视图对无需 CSRF 保护的视图(如公开 API),可使用 @csrf_exempt 装饰器:

    from django.views.decorators.csrf import csrf_exemptfrom django.http import JsonResponse@csrf_exemptdef public_api(request): return JsonResponse({'status': 'success'})
四、Django CSRF 防护的局限性及补充措施
  • 局限性

    对纯 GET 请求无效(因 GET 请求不应修改数据,Django 默认不验证)。

    若用户浏览器禁用 Cookie,需改用其他方式(如基于令牌的认证)。

  • 补充措施

    结合 SameSite Cookie 属性(如 SameSite=Lax 或 Strict)进一步限制跨站 Cookie 发送。

    使用 HTTPS 加密传输,防止令牌在传输过程中被窃取。

    对高风险操作(如支付)增加二次验证(如短信验证码)。

通过上述机制,Django 的 CSRF 防护能有效阻止攻击者利用用户浏览器发起恶意请求,同时保持对合法请求的兼容性。