跳转到内容

Django跨站请求伪造保护(CSRF Protection)

来自代码酷

Django跨站请求伪造保护(CSRF Protection)[编辑 | 编辑源代码]

跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种常见的网络攻击方式,攻击者利用用户已登录的身份,在用户不知情的情况下执行非预期的操作。Django内置了强大的CSRF保护机制,帮助开发者防范此类攻击。

什么是CSRF攻击?[编辑 | 编辑源代码]

CSRF攻击通常发生在以下场景:

  • 用户登录了某个网站(如银行网站),并在同一浏览器中访问了恶意网站。
  • 恶意网站包含一个表单或脚本,向银行网站发送请求(如转账操作)。
  • 由于用户已登录,银行网站会认为这是合法请求,导致攻击成功。

Django的CSRF保护机制通过以下方式防御:

  1. 为每个用户会话生成唯一的CSRF令牌(Token)。
  2. 要求所有修改数据的POST请求必须包含该令牌。
  3. 服务器验证令牌的有效性,拒绝无效请求。

Django中的CSRF实现[编辑 | 编辑源代码]

基本配置[编辑 | 编辑源代码]

Django默认启用CSRF中间件。在settings.py中确认以下中间件存在:

MIDDLEWARE = [
    'django.middleware.csrf.CsrfViewMiddleware',
    # 其他中间件...
]

在模板中使用CSRF令牌[编辑 | 编辑源代码]

任何包含POST表单的模板必须添加{% csrf_token %}标签:

<form method="post">
    {% csrf_token %}
    <input type="text" name="username">
    <button type="submit">提交</button>
</form>

渲染后的HTML会包含隐藏的CSRF令牌字段:

<form method="post">
    <input type="hidden" name="csrfmiddlewaretoken" value="32位随机字符串">
    <input type="text" name="username">
    <button type="submit">提交</button>
</form>

AJAX请求处理[编辑 | 编辑源代码]

对于AJAX请求,需要手动添加CSRF令牌。Django会在cookie中存储CSRF令牌(如果已启用CSRF中间件)。

使用JavaScript(jQuery示例):

$.ajax({
    url: "/api/endpoint/",
    type: "POST",
    data: {
        'content': "Hello World",
        'csrfmiddlewaretoken': getCookie('csrftoken')  // 从cookie获取
    },
    success: function(response) {
        console.log(response);
    }
});

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;
}

验证流程[编辑 | 编辑源代码]

Django的CSRF验证流程如下:

sequenceDiagram participant 客户端 participant 服务器 客户端->>服务器: GET请求(获取表单) 服务器->>客户端: 返回表单+CSRF令牌 客户端->>服务器: POST请求(包含CSRF令牌) 服务器->>服务器: 验证令牌 alt 令牌有效 服务器->>客户端: 200 OK else 令牌无效 服务器->>客户端: 403 Forbidden end

实际案例[编辑 | 编辑源代码]

案例1:银行转账攻击[编辑 | 编辑源代码]

假设银行网站有一个转账端点:

# views.py(危险!无CSRF保护)
def transfer_money(request):
    if request.method == 'POST':
        amount = request.POST['amount']
        account = request.POST['account']
        # 执行转账...
        return HttpResponse("转账成功")

攻击者可以构造恶意页面:

<!-- 恶意网站 -->
<form action="https://bank.com/transfer/" method="POST">
    <input type="hidden" name="amount" value="1000">
    <input type="hidden" name="account" value="attacker_account">
</form>
<script>document.forms[0].submit();</script>

启用CSRF保护后,这种攻击将失败,因为恶意页面无法获取有效的CSRF令牌。

案例2:安全实现[编辑 | 编辑源代码]

正确的Django视图应该:

from django.views.decorators.csrf import csrf_protect

@csrf_protect
def transfer_money(request):
    if request.method == 'POST':
        # 自动验证CSRF令牌
        amount = request.POST['amount']
        account = request.POST['account']
        # 执行转账...
        return HttpResponse("转账成功")
    # GET请求返回表单
    return render(request, 'transfer_form.html')

高级配置[编辑 | 编辑源代码]

豁免特定视图[编辑 | 编辑源代码]

有时需要禁用CSRF保护(如API端点),可以使用@csrf_exempt装饰器:

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def api_endpoint(request):
    # 这个视图不受CSRF保护
    return JsonResponse({'status': 'ok'})

自定义错误页面[编辑 | 编辑源代码]

当CSRF验证失败时,Django返回403错误。可以自定义处理:

from django.views.decorators.csrf import requires_csrf_token
from django.shortcuts import render

@requires_csrf_token
def my_csrf_failure_view(request, reason=""):
    return render(request, 'csrf_error.html', {'reason': reason}, status=403)

settings.py中配置:

CSRF_FAILURE_VIEW = 'myapp.views.my_csrf_failure_view'

数学原理[编辑 | 编辑源代码]

CSRF令牌通常是随机生成的,其安全性取决于熵值。假设令牌长度为32字节(256位),可能的组合数为: 22561.16×1077 这使得暴力破解在计算上不可行。

最佳实践[编辑 | 编辑源代码]

  • 始终为修改数据的请求启用CSRF保护
  • 不要通过GET请求修改数据
  • 对于API,考虑使用其他认证方式(如Token、JWT)
  • 保持Django版本更新,获取最新的安全补丁

常见问题[编辑 | 编辑源代码]

Q: 为什么我的表单提交返回403错误? A: 可能原因:

  • 模板中遗漏{% csrf_token %}
  • 中间件顺序错误(CsrfViewMiddleware应在SessionMiddleware之后)
  • 使用了缓存表单(CSRF令牌会过期)

Q: API如何实现CSRF保护? A: 推荐方式:

  • 对于传统API:使用csrf_protect并从cookie/header获取令牌
  • 对于现代SPA:使用JWT等无状态认证

通过理解并正确实现Django的CSRF保护,你可以有效防御这类常见的安全威胁,保护用户数据安全。