Django 安全最佳实践

Django 安全概述

Django 框架在设计时就考虑了安全性,提供了许多内置的安全特性。然而,正确配置和使用这些特性至关重要。本指南将介绍 Django 的安全最佳实践,帮助您构建安全的 Web 应用程序。

⚠️ 安全警告

安全是一个持续的过程,不是一次性任务。始终遵循最小权限原则,定期更新依赖,并进行安全审计。

Django 内置安全特性
  • CSRF 跨站请求伪造保护
  • XSS 跨站脚本攻击防护
  • SQL SQL 注入防护
  • Clickjacking 点击劫持防护
  • 认证 安全的用户认证系统
  • 数据验证 强大的表单和数据验证
常见 Web 攻击
攻击类型 风险等级 Django 防护
SQL 注入 严重 内置 ORM 防护
XSS 模板自动转义
CSRF CSRF 中间件
点击劫持 X-Frame-Options
会话劫持 安全的会话管理

安全配置设置

正确的 Django 配置是安全的基础。以下是在 settings.py 中必须配置的安全设置。

关键安全设置
settings.py - 安全配置
import os
from pathlib import Path

# 生产环境必须设置为 False
DEBUG = False

# 允许的主机列表
ALLOWED_HOSTS = [
    'yourdomain.com',
    'www.yourdomain.com',
    '127.0.0.1',  # 仅在开发时使用
]

# 安全密钥 - 生产环境使用环境变量
SECRET_KEY = os.environ.get('SECRET_KEY', 'fallback-secret-key-for-dev-only')

# HTTPS 设置
SECURE_SSL_REDIRECT = True  # 强制 HTTPS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True  # 仅通过 HTTPS 传输会话 Cookie
CSRF_COOKIE_SECURE = True     # 仅通过 HTTPS 传输 CSRF Cookie

# 安全头设置
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'  # 防止点击劫持

# HSTS 设置 (谨慎使用)
SECURE_HSTS_SECONDS = 31536000  # 1年
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
安全设置说明
设置 描述 风险等级
DEBUG 生产环境必须为 False,避免信息泄露 严重
ALLOWED_HOSTS 限制可服务的主机,防止主机头攻击
SECRET_KEY 使用强密钥,生产环境使用环境变量 严重
SECURE_SSL_REDIRECT 强制所有流量使用 HTTPS
X_FRAME_OPTIONS 防止点击劫持攻击
环境配置示例
环境特定的配置
import os

# 环境检测
ENVIRONMENT = os.environ.get('DJANGO_ENV', 'development')

if ENVIRONMENT == 'production':
    DEBUG = False
    ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
    SECURE_SSL_REDIRECT = True
    # 其他生产环境安全设置...
else:
    DEBUG = True
    ALLOWED_HOSTS = ['localhost', '127.0.0.1']
    # 开发环境设置...

CSRF 防护

跨站请求伪造 (CSRF) 攻击迫使登录用户在不知情的情况下执行非预期的操作。Django 提供了内置的 CSRF 保护机制。

用户登录
用户登录到可信网站
访问恶意网站
用户访问攻击者的网站
执行恶意请求
恶意网站发送伪造请求
攻击成功
用户账户执行非预期操作
启用 CSRF 保护
settings.py - 中间件
MIDDLEWARE = [
    # ...
    'django.middleware.csrf.CsrfViewMiddleware',
    # ...
]
模板中使用 CSRF Token
<form method="post">
    {% csrf_token %}
    <!-- 表单字段 -->
    <input type="text" name="username">
    <button type="submit">提交</button>
</form>

{# 对于 AJAX 请求 #}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

fetch('/api/endpoint/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': csrftoken,
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data)
});
</script>
视图中的 CSRF 处理
views.py
from django.views.decorators.csrf import csrf_exempt, csrf_protect
from django.utils.decorators import method_decorator
from django.views.generic import View

# 需要 CSRF 保护的视图(默认)
def normal_view(request):
    if request.method == 'POST':
        # CSRF 保护自动生效
        pass

# 豁免 CSRF 保护(谨慎使用)
@csrf_exempt
def exempt_view(request):
    # 这个视图不受 CSRF 保护
    pass

# 类视图的 CSRF 保护
class MyView(View):
    @method_decorator(csrf_protect)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)
⚠️ CSRF 保护注意事项
  • 确保所有 POST 表单都包含 {% csrf_token %}
  • AJAX 请求需要手动添加 CSRF token
  • 谨慎使用 @csrf_exempt,仅在必要时使用
  • 确保 CsrfViewMiddleware 在中间件中启用

XSS 防护

跨站脚本攻击 (XSS) 允许攻击者在受害者浏览器中执行恶意脚本。Django 模板系统提供自动转义来防止 XSS 攻击。

模板自动转义
模板中的自动转义
{# 自动转义 - 安全 #}
<p>{{ user_input }}</p>
{# 输出: <script>alert('XSS')</script> #}

{# 手动关闭转义 - 危险! #}
<p>{{ user_input|safe }}</p>
{# 输出: <script>alert('XSS')</script> #}

{# 条件性标记安全 #}
<p>{{ user_input|escape }}</p>
{# 显式转义 #}

{# 安全的HTML内容 #}
{% autoescape off %}
    <p>{{ trusted_html_content }}</p>
{% endautoescape %}
安全的 HTML 处理
使用 django-bleach
# 安装: pip install django-bleach

# forms.py
from django import forms
import bleach

class CommentForm(forms.Form):
    content = forms.CharField(widget=forms.Textarea)

    def clean_content(self):
        content = self.cleaned_data['content']
        # 清理 HTML,只允许安全的标签和属性
        cleaned_content = bleach.clean(
            content,
            tags=['p', 'br', 'strong', 'em', 'a'],
            attributes={'a': ['href', 'title']},
            strip=True
        )
        return cleaned_content
⚠️ XSS 防护最佳实践
  • 永远不要信任用户输入
  • 谨慎使用 |safe 过滤器
  • 对用户输入的 HTML 使用白名单清理
  • 设置正确的 Content-Type 头
  • 使用 CSP (Content Security Policy)

Content Security Policy (CSP)

安装 django-csp
# 安装
pip install django-csp
# settings.py
MIDDLEWARE = [
    # ...
    'csp.middleware.CSPMiddleware',
]

# CSP 配置
CSP_DEFAULT_SRC = ["'self'"]
CSP_SCRIPT_SRC = ["'self'", "https://cdn.example.com"]
CSP_STYLE_SRC = ["'self'", "'unsafe-inline'"]
CSP_IMG_SRC = ["'self'", "data:", "https:"]
CSP_FONT_SRC = ["'self'"]
CSP 的优势
  • 防止 XSS 攻击
  • 控制资源加载来源
  • 减少数据泄露风险
  • 符合现代安全标准
CSP 可能会影响网站功能,建议在开发阶段就实施并测试。

SQL 注入防护

Django 的 ORM 使用参数化查询,从根本上防止了 SQL 注入攻击。但不当使用仍然可能导致安全问题。

安全的数据库查询
安全的查询方法
from django.db import models
from django.contrib.auth.models import User

# 安全 - 使用 ORM
users = User.objects.filter(username=request.GET['username'])

# 安全 - 参数化查询
from django.db import connection
with connection.cursor() as cursor:
    cursor.execute("SELECT * FROM users WHERE username = %s", [username])
    results = cursor.fetchall()

# 危险 - 字符串拼接(绝对避免!)
dangerous_sql = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(dangerous_sql)
安全的原始查询
# 安全的原始查询
class UserManager(models.Manager):
    def search_users(self, search_term):
        return self.raw(
            'SELECT * FROM auth_user WHERE username LIKE %s',
            [f'%{search_term}%']  # 参数化,安全
        )

# 危险 - 直接字符串插值
def dangerous_search(search_term):
    return User.objects.raw(
        f"SELECT * FROM auth_user WHERE username LIKE '%{search_term}%'"
    )  # SQL 注入漏洞!
额外查询安全措施
查询安全最佳实践
from django.db.models import Q

# 使用 Q 对象进行复杂查询
def advanced_search(request):
    query = request.GET.get('q', '')
    filters = Q()

    if query:
        filters |= Q(username__icontains=query)
        filters |= Q(email__icontains=query)
        filters |= Q(first_name__icontains=query)

    return User.objects.filter(filters)

# 限制查询结果
def get_user_profile(request, user_id):
    # 确保用户只能访问自己的资料
    if request.user.id != user_id:
        raise PermissionDenied

    return User.objects.get(id=user_id)

# 使用 select_related 和 prefetch_related 优化查询
articles = Article.objects.select_related('author').prefetch_related('tags')
🚨 SQL 注入警告
  • 永远不要使用字符串拼接构建 SQL 查询
  • 谨慎使用 extra()RawSQL
  • 验证和清理所有用户输入
  • 使用 Django ORM 的默认方法
  • 定期进行安全代码审查

认证和授权

Django 提供了强大的认证系统,但正确配置和使用至关重要。

密码安全
密码验证器配置
# settings.py
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {
            'min_length': 8,
        }
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# 密码哈希设置
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
]
视图权限控制
基于函数的视图权限
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin

# 要求登录
@login_required
def profile_view(request):
    return render(request, 'profile.html')

# 要求特定权限
@permission_required('app.view_sensitive_data')
def sensitive_view(request):
    return render(request, 'sensitive.html')

# 基于类的视图权限
class AdminView(LoginRequiredMixin, PermissionRequiredMixin, View):
    permission_required = 'app.admin_access'
    login_url = '/login/'
    redirect_field_name = 'next'

会话安全

会话安全配置
# settings.py
# 会话设置
SESSION_COOKIE_AGE = 1209600  # 2周,默认
SESSION_COOKIE_SECURE = True   # 仅 HTTPS
SESSION_COOKIE_HTTPONLY = True # 防止 XSS 读取
SESSION_COOKIE_SAMESITE = 'Lax' # CSRF 防护

# 会话引擎(推荐使用数据库或缓存)
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
# 或者使用缓存
# SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

# 会话过期设置
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # 持久会话
SESSION_SAVE_EVERY_REQUEST = True        # 每次请求保存会话
会话安全最佳实践
  • 使用 HTTPS 传输会话 Cookie
  • 设置 HttpOnly 标志
  • 合理设置会话超时时间
  • 在密码更改时使旧会话失效
  • 监控异常会话活动
# 使特定会话失效
from django.contrib.sessions.models import Session

def logout_everywhere(request):
    # 删除当前会话以外的所有会话
    Session.objects.exclude(
        session_key=request.session.session_key
    ).delete()

文件上传安全

文件上传功能容易受到多种攻击,必须实施严格的安全措施。

安全的文件上传处理
安全的文件验证
import os
from django.core.files.storage import FileSystemStorage
from django.core.exceptions import ValidationError

def validate_file_extension(value):
    ext = os.path.splitext(value.name)[1]
    valid_extensions = ['.pdf', '.doc', '.docx', '.jpg', '.png', '.txt']
    if not ext.lower() in valid_extensions:
        raise ValidationError('不支持的文件类型')

def validate_file_size(value):
    filesize = value.size
    if filesize > 5 * 1024 * 1024:  # 5MB
        raise ValidationError("文件大小不能超过5MB")

class SecureFileForm(forms.Form):
    file = forms.FileField(
        validators=[validate_file_extension, validate_file_size],
        widget=forms.FileInput(attrs={
            'accept': '.pdf,.doc,.docx,.jpg,.png,.txt'
        })
    )
安全存储配置
安全文件存储
# settings.py
# 媒体文件安全设置
MEDIA_ROOT = '/var/www/media/'  # Web 根目录之外
MEDIA_URL = '/media/'

# 使用自定义存储后端
DEFAULT_FILE_STORAGE = 'myapp.storage.SecureFileStorage'

# 或者在视图级别控制
from django.core.files.storage import FileSystemStorage
from django.http import HttpResponseForbidden

class SecureStorage(FileSystemStorage):
    def get_available_name(self, name, max_length=None):
        # 防止目录遍历
        name = os.path.basename(name)
        return super().get_available_name(name, max_length)

def secure_file_download(request, file_path):
    # 验证用户权限
    if not request.user.has_perm('app.download_file'):
        return HttpResponseForbidden()

    # 防止路径遍历
    file_path = os.path.basename(file_path)
    # 提供文件下载...
⚠️ 文件上传安全警告
  • 永远不要信任上传的文件名
  • 限制允许的文件类型和大小
  • 将上传文件存储在 Web 根目录之外
  • 对图片进行重新处理以去除恶意代码
  • 使用防病毒软件扫描上传文件

部署安全检查清单

生产环境安全检查
DEBUG 模式已关闭

DEBUG = False

正确配置 ALLOWED_HOSTS

包含所有有效域名

⚠️
使用环境变量存储密钥

SECRET_KEY 不在代码中硬编码

⚠️
启用 HTTPS

所有流量强制使用 SSL/TLS

⚠️
数据库安全配置

使用强密码,限制网络访问

⚠️
定期更新依赖

使用 pip audit 检查漏洞

安全工具和命令

安全检查命令
# 检查安全配置
python manage.py check --deploy

# 检查依赖漏洞
pip audit
pip list --outdated

# 使用 safety 检查已知漏洞
pip install safety
safety check

# 使用 bandit 进行代码安全扫描
pip install bandit
bandit -r myproject/

# 使用 django-extensions 进行安全检查
python manage.py validate_templates
python manage.py show_urls
自动化安全扫描
# requirements-dev.txt
django-debug-toolbar==3.2.4
django-extensions==3.1.3
bandit==1.7.0
safety==1.10.3
pytest-bandit==0.6.1

# 在 CI/CD 中添加安全扫描
# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run Bandit
        run: |
          pip install bandit
          bandit -r myproject/ -f html -o bandit_report.html
      - name: Run Safety
        run: |
          pip install safety
          safety check --json > safety_report.json

持续安全监控

日志和监控
  • 启用详细的安全日志记录
  • 监控失败登录尝试
  • 记录敏感操作
  • 设置安全事件告警
  • 定期审查日志
# settings.py
LOGGING = {
    'version': 1,
    'handlers': {
        'security_file': {
            'level': 'WARNING',
            'class': 'logging.FileHandler',
            'filename': '/var/log/django/security.log',
        },
    },
    'loggers': {
        'django.security': {
            'handlers': ['security_file'],
            'level': 'WARNING',
            'propagate': False,
        },
    },
}
应急响应计划
  • 建立安全事件响应流程
  • 准备数据备份和恢复方案
  • 制定沟通计划
  • 定期进行安全演练
  • 保持安全文档更新
应急响应步骤
  1. 确认安全事件
  2. 隔离受影响系统
  3. 收集证据和日志
  4. 修复漏洞
  5. 恢复服务
  6. 事后分析和改进
🔐 安全文化

安全不仅仅是技术问题,更是文化问题。建立安全意识,进行安全培训,让每个团队成员都成为应用安全的第一道防线。