刚写完的django小项目上线没两天,就收到了安全扫描的SQL注入/XSS预警?部署后访问慢得离谱还找不到关键日志?这篇博客整理了生产级django的安全配置到部署落地全流程,踩过的坑和核心最佳实践都浓缩在这里了。


本文你会学到

  • django常见安全威胁及内置防护功能
  • settings.py的生产环境安全硬配置
  • 输入验证、模型层净化、CSRF/XSS加固的落地代码
  • Docker+Gunicorn+Postgres的轻量部署方案
  • 缓存、查询优化的小技巧
  • 基础监控和部署前必查清单

django安全先打底

先理清楚要防什么

django应用上线前,先盯着这几个高频漏洞跑:

  • SQL注入:绕不开,好在django ORM自动防
  • XSS:用户输入存恶意HTML/JS渲染出来
  • CSRF:诱导用户在已登录站点提交假请求
  • 点击劫持:透明iframe覆盖按钮盗操作
  • 暴力破解:批量试密码

django自带的“安全盾”

别重复造轮子!先把这些开了:

  • 模板自动转义XSS
  • 内置CSRF中间件
  • ORM自动防SQL注入
  • X-Frame-Options防点击劫持
  • 可扩展的用户认证/权限系统

敲黑板!settings.py生产环境硬配置

# production_settings.py(必须和base分离!!!)
import os
from pathlib import Path
from .base import *

BASE_DIR = Path(__file__).resolve(strict=True).parent.parent

# 1. 最核心的配置
DEBUG = False  # 绝对!绝对!绝对!不能是True!!!
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")  # 放环境变量,别写代码里
ALLOWED_HOSTS = ["yourdomain.com", "www.yourdomain.com"]  # 别用*,只加生产域名

# 2. HTTPS相关(必须全站HTTPS)
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 强制浏览器用HTTPS访问1年
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True  # 防止JS读Session Cookie
CSRF_COOKIE_HTTPONLY = True  # 可选,增强CSRF防护
SESSION_COOKIE_SAMESITE = "Strict"
CSRF_COOKIE_SAMESITE = "Strict"

# 3. 其他安全头部
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"

# 4. 数据库(Postgres推荐,必开SSL)
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ.get("DB_NAME"),
        "USER": os.environ.get("DB_USER"),
        "PASSWORD": os.environ.get("DB_PASSWORD"),
        "HOST": os.environ.get("DB_HOST"),
        "PORT": "5432",
        "OPTIONS": {"sslmode": "require"},
        "CONN_MAX_AGE": 600,  # 连接池,减少开销
    }
}

# 5. 密码验证(别用默认的短密码!!!)
AUTH_PASSWORD_VALIDATORS = [
    {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
    {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 12}},
    {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
    {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]

# 6. 防暴力破解(用django-axes)
INSTALLED_APPS += ["axes"]
AUTHENTICATION_BACKENDS = [
    "axes.backends.AxesStandaloneBackend",
    "django.contrib.auth.backends.ModelBackend",
]
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 1  # 1小时后解锁
AXES_LOCK_OUT_BY_COMBINATION_USER_AND_IP = True

输入和模型层别放松

模板自动转义XSS不够!还要在表单层和模型层双重验证+净化,这里用django推荐的bleach库处理富文本。

表单层安全

# forms.py
from django import forms
from django.core.validators import RegexValidator
import bleach

class SafeContactForm(forms.Form):
    name = forms.CharField(
        max_length=100,
        validators=[RegexValidator(r"^[a-zA-Z\s]+$", "姓名只能含字母和空格")]
    )
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

    # 单独净化message
    def clean_message(self):
        message = self.cleaned_data["message"]
        # 这里只允许纯文本,如果有富文本需求再开allowed_tags
        return bleach.clean(message, tags=[], strip=True)

    # 全局验证垃圾内容
    def clean(self):
        cleaned_data = super().clean()
        message = cleaned_data.get("message", "").lower()
        spam_keywords = ["viagra", "casino", "loan"]
        if any(k in message for k in spam_keywords):
            raise forms.ValidationError("消息包含不安全内容")
        return cleaned_data

模型层补漏

# models.py
from django.db import models
from django.core.exceptions import ValidationError
import bleach

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()  # 富文本场景
    author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)

    # 模型验证
    def clean(self):
        # 检查标题有没有恶意JS开头
        if self.title.lower().startswith("javascript:"):
            raise ValidationError("标题包含不安全内容")

    # 保存前净化富文本
    def save(self, *args, **kwargs):
        allowed_tags = ["p", "br", "strong", "em", "ul", "ol", "li"]
        allowed_attrs = {}
        self.content = bleach.clean(self.content, tags=allowed_tags, attributes=allowed_attrs, strip=True)
        super().save(*args, **kwargs)

CSRF防护小补充

默认的CSRF中间件已经覆盖表单提交,但要注意AJAX的情况:

// 前端JS(jQuery为例)
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
            // 只对同域请求加CSRF Token
            xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
        }
    }
});

// 获取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;
}

轻量部署:Docker+Gunicorn

别直接用python manage.py runserver!那是开发环境用的,不安全也慢。

Dockerfile

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# 安装系统依赖(Postgres客户端)
RUN apt-get update && apt-get install -y --no-install-recommends \
    postgresql-client \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖并安装(缓存层优化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 【重要】创建非root用户运行
RUN useradd --create-home --shell /bin/bash app
RUN chown -R app:app /app
USER app

# 收集静态文件
RUN python manage.py collectstatic --noinput

EXPOSE 8000

# 启动Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "myproject.wsgi:application"]

docker-compose.yml

不用单独装Postgres和Redis,一键启动:

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SETTINGS_MODULE=myproject.production_settings
      - DB_HOST=db
      - DJANGO_SECRET_KEY=your_secure_secret_key_here
      - DB_NAME=myproject
      - DB_USER=postgres
      - DB_PASSWORD=your_secure_db_password_here
    depends_on:
      - db
    volumes:
      - static_volume:/app/staticfiles
      - media_volume:/app/media
    restart: always

  db:
    image: postgres:15-alpine
    environment:
      - POSTGRES_DB=myproject
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=your_secure_db_password_here
    volumes:
      - postgres_data:/var/lib/postgresql/data
    restart: always

  # 可选:Nginx作为反向代理+静态文件服务器
  # nginx:
  #   image: nginx:alpine
  #   ports:
  #     - "80:80"
  #     - "443:443"
  #   volumes:
  #     - ./nginx/conf.d:/etc/nginx/conf.d
  #     - static_volume:/app/staticfiles
  #     - media_volume:/app/media
  #     - ./ssl:/etc/nginx/ssl
  #   depends_on:
  #     - web
  #   restart: always

volumes:
  postgres_data:
  static_volume:
  media_volume:

性能和监控小收尾

简单的缓存优化

django-redis缓存高频访问的数据:

# settings.py(加在生产配置里)
INSTALLED_APPS += ["django_redis"]
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://redis:6379/1",
        "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
    }
}
# views.py
from django.views.decorators.cache import cache_page

@cache_page(60 * 15)  # 缓存15分钟
def article_list_view(request):
    articles = Article.objects.published().select_related("author")  # 同时优化查询
    return render(request, "articles/list.html", {"articles": articles})

部署前必查清单(全勾了再上线!)

  • DEBUG = False
  • SECRET_KEY和数据库密码全在环境变量里
  • ALLOWED_HOSTS只有生产域名
  • 全站HTTPS,安全头部已配置
  • 静态文件已收集,权限正确
  • 数据库已开SSL,连接池已加
  • django-axes已配置防暴力破解
  • 日志已配置,路径有写权限
  • 所有依赖包已更新到最新安全版本

总结与下一步

安全不是一劳永逸的,要定期更新django和依赖包(用pip-audit扫),定期做OWASP ZAP的安全扫描;性能也要持续监控,根据访问模式调整缓存时间和查询。下一步可以试试:

  • 用Nginx作为反向代理+静态文件服务器(比Gunicorn快)
  • 用Let’s Encrypt免费申请SSL证书
  • 用Sentry增强错误监控
  • 用Celery处理异步任务

希望这篇博客能帮你把django项目安全、快速地部署上线!