django-security

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Django Security Best Practices

Django安全最佳实践

Comprehensive security guidelines for Django applications to protect against common vulnerabilities.
Django应用的全面安全指南,用于防范常见漏洞。

When to Activate

适用场景

  • Setting up Django authentication and authorization
  • Implementing user permissions and roles
  • Configuring production security settings
  • Reviewing Django application for security issues
  • Deploying Django applications to production
  • 配置Django身份验证与授权
  • 实现用户权限与角色管理
  • 配置生产环境安全设置
  • 审核Django应用的安全问题
  • 将Django应用部署到生产环境

Core Security Settings

核心安全设置

Production Settings Configuration

生产环境配置

python
undefined
python
undefined

settings/production.py

settings/production.py

import os
DEBUG = False # CRITICAL: Never use True in production
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
import os
DEBUG = False # 关键:生产环境中绝不能设置为True
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

Security headers

安全响应头

SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True X_FRAME_OPTIONS = 'DENY'
SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1年 SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True X_FRAME_OPTIONS = 'DENY'

HTTPS and Cookies

HTTPS与Cookie设置

SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_SAMESITE = 'Lax'

Secret key (must be set via environment variable)

密钥(必须通过环境变量设置)

SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') if not SECRET_KEY: raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY') if not SECRET_KEY: raise ImproperlyConfigured('必须设置DJANGO_SECRET_KEY环境变量')

Password validation

密码验证

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', }, ]
undefined
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', }, ]
undefined

Authentication

身份验证

Custom User Model

自定义用户模型

python
undefined
python
undefined

apps/users/models.py

apps/users/models.py

from django.contrib.auth.models import AbstractUser from django.db import models
class User(AbstractUser): """Custom user model for better security."""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)

USERNAME_FIELD = 'email'  # Use email as username
REQUIRED_FIELDS = ['username']

class Meta:
    db_table = 'users'
    verbose_name = 'User'
    verbose_name_plural = 'Users'

def __str__(self):
    return self.email
from django.contrib.auth.models import AbstractUser from django.db import models
class User(AbstractUser): """自定义用户模型以提升安全性。"""
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, blank=True)

USERNAME_FIELD = 'email'  # 使用邮箱作为用户名
REQUIRED_FIELDS = ['username']

class Meta:
    db_table = 'users'
    verbose_name = '用户'
    verbose_name_plural = '用户'

def __str__(self):
    return self.email

settings/base.py

settings/base.py

AUTH_USER_MODEL = 'users.User'
undefined
AUTH_USER_MODEL = 'users.User'
undefined

Password Hashing

密码哈希

python
undefined
python
undefined

Django uses PBKDF2 by default. For stronger security:

Django默认使用PBKDF2。如需更强安全性:

PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]
undefined
PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ]
undefined

Session Management

会话管理

python
undefined
python
undefined

Session configuration

会话配置

SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # Or 'db' SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_AGE = 3600 * 24 * 7 # 1 week SESSION_SAVE_EVERY_REQUEST = False SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Better UX, but less secure
undefined
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 或'db' SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_AGE = 3600 * 24 * 7 # 1周 SESSION_SAVE_EVERY_REQUEST = False SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 用户体验更好,但安全性略低
undefined

Authorization

授权

Permissions

权限设置

python
undefined
python
undefined

models.py

models.py

from django.db import models from django.contrib.auth.models import Permission
class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
    permissions = [
        ('can_publish', 'Can publish posts'),
        ('can_edit_others', 'Can edit posts of others'),
    ]

def user_can_edit(self, user):
    """Check if user can edit this post."""
    return self.author == user or user.has_perm('app.can_edit_others')
from django.db import models from django.contrib.auth.models import Permission
class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
    permissions = [
        ('can_publish', '可以发布文章'),
        ('can_edit_others', '可以编辑他人文章'),
    ]

def user_can_edit(self, user):
    """检查用户是否可以编辑该文章。"""
    return self.author == user or user.has_perm('app.can_edit_others')

views.py

views.py

from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.views.generic import UpdateView
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = Post permission_required = 'app.can_edit_others' raise_exception = True # Return 403 instead of redirect
def get_queryset(self):
    """Only allow users to edit their own posts."""
    return Post.objects.filter(author=self.request.user)
undefined
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin from django.views.generic import UpdateView
class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): model = Post permission_required = 'app.can_edit_others' raise_exception = True # 返回403而不是重定向
def get_queryset(self):
    """仅允许用户编辑自己的文章。"""
    return Post.objects.filter(author=self.request.user)
undefined

Custom Permissions

自定义权限

python
undefined
python
undefined

permissions.py

permissions.py

from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission): """Allow only owners to edit objects."""
def has_object_permission(self, request, view, obj):
    # Read permissions allowed for any request
    if request.method in permissions.SAFE_METHODS:
        return True

    # Write permissions only for owner
    return obj.author == request.user
class IsAdminOrReadOnly(permissions.BasePermission): """Allow admins to do anything, others read-only."""
def has_permission(self, request, view):
    if request.method in permissions.SAFE_METHODS:
        return True
    return request.user and request.user.is_staff
class IsVerifiedUser(permissions.BasePermission): """Allow only verified users."""
def has_permission(self, request, view):
    return request.user and request.user.is_authenticated and request.user.is_verified
undefined
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission): """仅允许所有者编辑对象。"""
def has_object_permission(self, request, view, obj):
    # 所有请求都允许读取权限
    if request.method in permissions.SAFE_METHODS:
        return True

    # 仅所有者拥有写入权限
    return obj.author == request.user
class IsAdminOrReadOnly(permissions.BasePermission): """允许管理员执行任何操作,其他用户仅可读取。"""
def has_permission(self, request, view):
    if request.method in permissions.SAFE_METHODS:
        return True
    return request.user and request.user.is_staff
class IsVerifiedUser(permissions.BasePermission): """仅允许已验证用户访问。"""
def has_permission(self, request, view):
    return request.user and request.user.is_authenticated and request.user.is_verified
undefined

Role-Based Access Control (RBAC)

基于角色的访问控制(RBAC)

python
undefined
python
undefined

models.py

models.py

from django.contrib.auth.models import AbstractUser, Group
class User(AbstractUser): ROLE_CHOICES = [ ('admin', 'Administrator'), ('moderator', 'Moderator'), ('user', 'Regular User'), ] role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
def is_admin(self):
    return self.role == 'admin' or self.is_superuser

def is_moderator(self):
    return self.role in ['admin', 'moderator']
from django.contrib.auth.models import AbstractUser, Group
class User(AbstractUser): ROLE_CHOICES = [ ('admin', '管理员'), ('moderator', '版主'), ('user', '普通用户'), ] role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')
def is_admin(self):
    return self.role == 'admin' or self.is_superuser

def is_moderator(self):
    return self.role in ['admin', 'moderator']

Mixins

混入类

class AdminRequiredMixin: """Mixin to require admin role."""
def dispatch(self, request, *args, **kwargs):
    if not request.user.is_authenticated or not request.user.is_admin():
        from django.core.exceptions import PermissionDenied
        raise PermissionDenied
    return super().dispatch(request, *args, **kwargs)
undefined
class AdminRequiredMixin: """要求管理员角色的混入类。"""
def dispatch(self, request, *args, **kwargs):
    if not request.user.is_authenticated or not request.user.is_admin():
        from django.core.exceptions import PermissionDenied
        raise PermissionDenied
    return super().dispatch(request, *args, **kwargs)
undefined

SQL Injection Prevention

SQL注入防范

Django ORM Protection

Django ORM防护

python
undefined
python
undefined

GOOD: Django ORM automatically escapes parameters

安全:Django ORM会自动转义参数

def get_user(username): return User.objects.get(username=username) # Safe
def get_user(username): return User.objects.get(username=username) # 安全

GOOD: Using parameters with raw()

安全:在raw()中使用参数

def search_users(query): return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
def search_users(query): return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])

BAD: Never directly interpolate user input

危险:绝不要直接拼接用户输入

def get_user_bad(username): return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # VULNERABLE!
def get_user_bad(username): return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # 存在漏洞!

GOOD: Using filter with proper escaping

安全:使用带正确转义的filter

def get_users_by_email(email): return User.objects.filter(email__iexact=email) # Safe
def get_users_by_email(email): return User.objects.filter(email__iexact=email) # 安全

GOOD: Using Q objects for complex queries

安全:使用Q对象进行复杂查询

from django.db.models import Q def search_users_complex(query): return User.objects.filter( Q(username__icontains=query) | Q(email__icontains=query) ) # Safe
undefined
from django.db.models import Q def search_users_complex(query): return User.objects.filter( Q(username__icontains=query) | Q(email__icontains=query) ) # 安全
undefined

Extra Security with raw()

raw()的额外安全措施

python
undefined
python
undefined

If you must use raw SQL, always use parameters

若必须使用原生SQL,务必使用参数

User.objects.raw( 'SELECT * FROM users WHERE email = %s AND status = %s', [user_input_email, status] )
undefined
User.objects.raw( 'SELECT * FROM users WHERE email = %s AND status = %s', [user_input_email, status] )
undefined

XSS Prevention

XSS防范

Template Escaping

模板转义

django
{# Django auto-escapes variables by default - SAFE #}
{{ user_input }}  {# Escaped HTML #}

{# Explicitly mark safe only for trusted content #}
{{ trusted_html|safe }}  {# Not escaped #}

{# Use template filters for safe HTML #}
{{ user_input|escape }}  {# Same as default #}
{{ user_input|striptags }}  {# Remove all HTML tags #}

{# JavaScript escaping #}
<script>
    var username = {{ username|escapejs }};
</script>
django
{# Django默认自动转义变量 - 安全 #}
{{ user_input }}  {# 已转义HTML #}

{# 仅对可信内容显式标记为安全 #}
{{ trusted_html|safe }}  {# 不转义 #}

{# 使用模板过滤器处理安全HTML #}
{{ user_input|escape }}  {# 与默认行为相同 #}
{{ user_input|striptags }}  {# 移除所有HTML标签 #}

{# JavaScript转义 #}
<script>
    var username = {{ username|escapejs }};
</script>

Safe String Handling

安全字符串处理

python
from django.utils.safestring import mark_safe
from django.utils.html import escape
python
from django.utils.safestring import mark_safe
from django.utils.html import escape

BAD: Never mark user input as safe without escaping

危险:绝不要在未转义的情况下将用户输入标记为安全

def render_bad(user_input): return mark_safe(user_input) # VULNERABLE!
def render_bad(user_input): return mark_safe(user_input) # 存在漏洞!

GOOD: Escape first, then mark safe

安全:先转义,再标记为安全

def render_good(user_input): return mark_safe(escape(user_input))
def render_good(user_input): return mark_safe(escape(user_input))

GOOD: Use format_html for HTML with variables

安全:使用format_html处理带变量的HTML

from django.utils.html import format_html
def greet_user(username): return format_html('<span class="user">{}</span>', escape(username))
undefined
from django.utils.html import format_html
def greet_user(username): return format_html('<span class="user">{}</span>', escape(username))
undefined

HTTP Headers

HTTP响应头

python
undefined
python
undefined

settings.py

settings.py

SECURE_CONTENT_TYPE_NOSNIFF = True # Prevent MIME sniffing SECURE_BROWSER_XSS_FILTER = True # Enable XSS filter X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking
SECURE_CONTENT_TYPE_NOSNIFF = True # 防止MIME类型嗅探 SECURE_BROWSER_XSS_FILTER = True # 启用XSS过滤器 X_FRAME_OPTIONS = 'DENY' # 防止点击劫持

Custom middleware

自定义中间件

from django.conf import settings
class SecurityHeaderMiddleware: def init(self, get_response): self.get_response = get_response
def __call__(self, request):
    response = self.get_response(request)
    response['X-Content-Type-Options'] = 'nosniff'
    response['X-Frame-Options'] = 'DENY'
    response['X-XSS-Protection'] = '1; mode=block'
    response['Content-Security-Policy'] = "default-src 'self'"
    return response
undefined
from django.conf import settings
class SecurityHeaderMiddleware: def init(self, get_response): self.get_response = get_response
def __call__(self, request):
    response = self.get_response(request)
    response['X-Content-Type-Options'] = 'nosniff'
    response['X-Frame-Options'] = 'DENY'
    response['X-XSS-Protection'] = '1; mode=block'
    response['Content-Security-Policy'] = "default-src 'self'"
    return response
undefined

CSRF Protection

CSRF防护

Default CSRF Protection

默认CSRF防护

python
undefined
python
undefined

settings.py - CSRF is enabled by default

settings.py - CSRF默认已启用

CSRF_COOKIE_SECURE = True # Only send over HTTPS CSRF_COOKIE_HTTPONLY = True # Prevent JavaScript access CSRF_COOKIE_SAMESITE = 'Lax' # Prevent CSRF in some cases CSRF_TRUSTED_ORIGINS = ['https://example.com'] # Trusted domains
CSRF_COOKIE_SECURE = True # 仅通过HTTPS发送 CSRF_COOKIE_HTTPONLY = True # 防止JavaScript访问 CSRF_COOKIE_SAMESITE = 'Lax' # 在某些场景下防范CSRF CSRF_TRUSTED_ORIGINS = ['https://example.com'] # 可信域名

Template usage

模板使用

<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">Submit</button> </form>
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">提交</button> </form>

AJAX requests

AJAX请求

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; }
fetch('/api/endpoint/', { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/json', }, body: JSON.stringify(data) });
undefined
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; }
fetch('/api/endpoint/', { method: 'POST', headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/json', }, body: JSON.stringify(data) });
undefined

Exempting Views (Use Carefully)

视图豁免(谨慎使用)

python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # Only use when absolutely necessary!
def webhook_view(request):
    # Webhook from external service
    pass
python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # 仅在绝对必要时使用!
def webhook_view(request):
    # 来自外部服务的Webhook
    pass

File Upload Security

文件上传安全

File Validation

文件验证

python
import os
from django.core.exceptions import ValidationError

def validate_file_extension(value):
    """Validate file extension."""
    ext = os.path.splitext(value.name)[1]
    valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
    if not ext.lower() in valid_extensions:
        raise ValidationError('Unsupported file extension.')

def validate_file_size(value):
    """Validate file size (max 5MB)."""
    filesize = value.size
    if filesize > 5 * 1024 * 1024:
        raise ValidationError('File too large. Max size is 5MB.')
python
import os
from django.core.exceptions import ValidationError

def validate_file_extension(value):
    """验证文件扩展名。"""
    ext = os.path.splitext(value.name)[1]
    valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
    if not ext.lower() in valid_extensions:
        raise ValidationError('不支持的文件扩展名。')

def validate_file_size(value):
    """验证文件大小(最大5MB)。"""
    filesize = value.size
    if filesize > 5 * 1024 * 1024:
        raise ValidationError('文件过大。最大允许大小为5MB。')

models.py

models.py

class Document(models.Model): file = models.FileField( upload_to='documents/', validators=[validate_file_extension, validate_file_size] )
undefined
class Document(models.Model): file = models.FileField( upload_to='documents/', validators=[validate_file_extension, validate_file_size] )
undefined

Secure File Storage

安全文件存储

python
undefined
python
undefined

settings.py

settings.py

MEDIA_ROOT = '/var/www/media/' MEDIA_URL = '/media/'
MEDIA_ROOT = '/var/www/media/' MEDIA_URL = '/media/'

Use a separate domain for media in production

生产环境中为媒体文件使用独立域名

MEDIA_DOMAIN = 'https://media.example.com'
MEDIA_DOMAIN = 'https://media.example.com'

Don't serve user uploads directly

不要直接提供用户上传的文件

Use whitenoise or a CDN for static files

使用whitenoise或CDN提供静态文件

Use a separate server or S3 for media files

使用独立服务器或S3存储媒体文件

undefined
undefined

API Security

API安全

Rate Limiting

速率限制

python
undefined
python
undefined

settings.py

settings.py

REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day', 'upload': '10/hour', } }
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ], 'DEFAULT_THROTTLE_RATES': { 'anon': '100/day', 'user': '1000/day', 'upload': '10/hour', } }

Custom throttle

自定义速率限制

from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle): scope = 'burst' rate = '60/min'
class SustainedRateThrottle(UserRateThrottle): scope = 'sustained' rate = '1000/day'
undefined
from rest_framework.throttling import UserRateThrottle
class BurstRateThrottle(UserRateThrottle): scope = 'burst' rate = '60/min'
class SustainedRateThrottle(UserRateThrottle): scope = 'sustained' rate = '1000/day'
undefined

Authentication for APIs

API身份验证

python
undefined
python
undefined

settings.py

settings.py

REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], }
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ], }

views.py

views.py

from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST']) @permission_classes([IsAuthenticated]) def protected_view(request): return Response({'message': 'You are authenticated'})
undefined
from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated
@api_view(['GET', 'POST']) @permission_classes([IsAuthenticated]) def protected_view(request): return Response({'message': '您已通过身份验证'})
undefined

Security Headers

安全响应头

Content Security Policy

内容安全策略

python
undefined
python
undefined

settings.py

settings.py

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_CONNECT_SRC = "'self' https://api.example.com"
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_CONNECT_SRC = "'self' https://api.example.com"

Middleware

中间件

class CSPMiddleware: def init(self, get_response): self.get_response = get_response
def __call__(self, request):
    response = self.get_response(request)
    response['Content-Security-Policy'] = (
        f"default-src {CSP_DEFAULT_SRC}; "
        f"script-src {CSP_SCRIPT_SRC}; "
        f"style-src {CSP_STYLE_SRC}; "
        f"img-src {CSP_IMG_SRC}; "
        f"connect-src {CSP_CONNECT_SRC}"
    )
    return response
undefined
class CSPMiddleware: def init(self, get_response): self.get_response = get_response
def __call__(self, request):
    response = self.get_response(request)
    response['Content-Security-Policy'] = (
        f"default-src {CSP_DEFAULT_SRC}; "
        f"script-src {CSP_SCRIPT_SRC}; "
        f"style-src {CSP_STYLE_SRC}; "
        f"img-src {CSP_IMG_SRC}; "
        f"connect-src {CSP_CONNECT_SRC}"
    )
    return response
undefined

Environment Variables

环境变量

Managing Secrets

密钥管理

python
undefined
python
undefined

Use python-decouple or django-environ

使用python-decouple或django-environ

import environ
env = environ.Env( # set casting, default value DEBUG=(bool, False) )
import environ
env = environ.Env( # 设置类型转换与默认值 DEBUG=(bool, False) )

reading .env file

读取.env文件

environ.Env.read_env()
SECRET_KEY = env('DJANGO_SECRET_KEY') DATABASE_URL = env('DATABASE_URL') ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
environ.Env.read_env()
SECRET_KEY = env('DJANGO_SECRET_KEY') DATABASE_URL = env('DATABASE_URL') ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')

.env file (never commit this)

.env文件(绝不要提交到版本控制系统)

DEBUG=False SECRET_KEY=your-secret-key-here DATABASE_URL=postgresql://user:password@localhost:5432/dbname ALLOWED_HOSTS=example.com,www.example.com
undefined
DEBUG=False SECRET_KEY=your-secret-key-here DATABASE_URL=postgresql://user:password@localhost:5432/dbname ALLOWED_HOSTS=example.com,www.example.com
undefined

Logging Security Events

安全事件日志

python
undefined
python
undefined

settings.py

settings.py

LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': '/var/log/django/security.log', }, 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.security': { 'handlers': ['file', 'console'], 'level': 'WARNING', 'propagate': True, }, 'django.request': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': False, }, }, }
undefined
LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'WARNING', 'class': 'logging.FileHandler', 'filename': '/var/log/django/security.log', }, 'console': { 'level': 'INFO', 'class': 'logging.StreamHandler', }, }, 'loggers': { 'django.security': { 'handlers': ['file', 'console'], 'level': 'WARNING', 'propagate': True, }, 'django.request': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': False, }, }, }
undefined

Quick Security Checklist

快速安全检查清单

CheckDescription
DEBUG = False
Never run with DEBUG in production
HTTPS onlyForce SSL, secure cookies
Strong secretsUse environment variables for SECRET_KEY
Password validationEnable all password validators
CSRF protectionEnabled by default, don't disable
XSS preventionDjango auto-escapes, don't use
&#124;safe
with user input
SQL injectionUse ORM, never concatenate strings in queries
File uploadsValidate file type and size
Rate limitingThrottle API endpoints
Security headersCSP, X-Frame-Options, HSTS
LoggingLog security events
UpdatesKeep Django and dependencies updated
Remember: Security is a process, not a product. Regularly review and update your security practices.
检查项描述
DEBUG = False
生产环境中绝不能开启DEBUG模式
仅使用HTTPS强制使用SSL,配置安全Cookie
强密钥使用环境变量存储SECRET_KEY
密码验证启用所有密码验证器
CSRF防护默认已启用,请勿随意禁用
XSS防范Django自动转义,不要对用户输入使用
&#124;safe
SQL注入防范使用ORM,绝不要在查询中拼接字符串
文件上传验证文件类型与大小
速率限制对API端点进行限流
安全响应头配置CSP、X-Frame-Options、HSTS
日志记录记录安全事件
更新维护保持Django及依赖库为最新版本
注意:安全是一个持续的过程,而非一次性产品。请定期审核并更新您的安全实践。