Just two days after the small Django project I just finished went online, I received a SQL injection/XSS warning from a security scan? After deployment, access is ridiculously slow and key logs cannot be found? This blog organizes the entire process from security configuration to deployment of production-level Django. The pitfalls and core best practices are condensed here.
In this article you will learn
- Django common security threats and built-in protection functions
- Security hard configuration for production environment in settings.py
- Implementation code for input verification, model layer purification, and CSRF/XSS reinforcement
- Docker+Gunicorn+Postgres lightweight deployment solution
- Tips on caching and query optimization
- Basic monitoring and pre-deployment checklist
Django security first
Before launching the Django application, first keep an eye on these high-frequency vulnerabilities:
- SQL injection: cannot be avoided, fortunately django ORM automatically prevents it
- XSS: User input is stored in malicious HTML/JS and rendered out
- CSRF: induce users to submit fake requests on logged-in sites
- Click Hijacking: Transparent iframe overlays buttons to steal actions
- Brute force cracking: try passwords in batches
Django’s own “security shield”
Don’t reinvent the wheel! Let’s open these first:
- Templates automatically escape XSS
- Built-in CSRF middleware
- ORM automatically prevents SQL injection
- X-Frame-Options anti-clickjacking
- Extensible user authentication/authorization system
Knock on the blackboard! settings.py production environment hard configuration
# 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
Templates automatically escaping XSS is not enough! It is also necessary to perform double verification + purification at the form layer and model layer. The one recommended by Django is used here.bleachLibrary for handling rich text.
# 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
Model layer leak repair
# 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 protection small supplement
The default CSRF middleware already covers form submission, but pay attention to the AJAX situation:
// 前端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;
}
Lightweight deployment: Docker+Gunicorn
Don't use it directlypython manage.py runserver! That's for development environments, it's unsafe and slow.
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
No need to install Postgres and Redis separately, start them with one click:
# 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:
Simple cache optimization
usedjango-redisCache frequently accessed data:
# 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})
Must-check checklist before deployment (check all the boxes before going online!)
Summary and next steps
Security is not something that can be done once and for all. Django and dependent packages must be updated regularly (usepip-auditScan), perform regular security scans of OWASP ZAP; performance must also be continuously monitored, and cache time and queries must be adjusted according to access patterns. Next step you can try:
- Use Nginx as reverse proxy + static file server (faster than Gunicorn)
- Apply for a free SSL certificate with Let’s Encrypt
- Enhance error monitoring with Sentry
- Handle asynchronous tasks with Celery
I hope this blog can help you deploy your Django project safely and quickly!