Communitygithub.com

django-security

Django 安全最佳實踐:驗證、授權、CSRF 防護、SQL 注入防護、XSS 防護與安全部署設定。適用於檢視 Django 驗證、授權、輸入處理或部署設定時。

django-security 是什麼?

這項技能提供了全面的 Django 安全指南,旨在協助開發者保護應用程式免受常見漏洞攻擊。它涵蓋了從基礎的生產環境設定(如停用 DEBUG、設定安全標頭)到進階的授權機制(包括權限管理、角色型存取控制)等核心安全主題。此外,技能詳細說明了如何有效防範 SQL 注入(使用 Django ORM 的參數化查詢)、XSS 攻擊(模板轉義與安全字串處理),以及 CSRF 攻擊(Cookie 設定與 JavaScript 處理)。最後,也包含了檔案上傳驗證、API 安全性(速率限制與 JWT 認證)以及安全部署配置等實務建議,支援 Django 專案的完整開發與運維生命週期。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/django-security

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Django 安全最佳實踐

全面的 Django 應用程式安全指南,保護您的應用程式免受常見漏洞攻擊。

啟動時機

  • 設定 Django 驗證與授權
  • 實作用戶權限與角色
  • 配置生產環境安全設定
  • 檢視 Django 應用程式的安全問題
  • 將 Django 應用程式部署至生產環境

核心安全設定

生產環境設定配置

# settings/production.py
import os

DEBUG = False  # 關鍵:絕對不能在生產環境設為 True

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# 安全標頭
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 與 Cookie
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'

# Secret key(必須透過環境變數設定)
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured('需要設定 DJANGO_SECRET_KEY 環境變數')

# 密碼驗證
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',
    },
]

驗證

自訂用戶模型

# apps/users/models.py
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
AUTH_USER_MODEL = 'users.User'

密碼雜湊

# Django 預設使用 PBKDF2。如需更強的安全性:
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

Session 管理

# Session 配置
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  # 使用者體驗較佳,但較不安全

授權

權限

# 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_edit_others', '可以編輯他人的文章'),
        ]

    def user_can_edit(self, user):
        """檢查使用者能否編輯此文章。"""
        return self.author == user or user.has_perm('app.can_edit_others')

# 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  # 回傳 403 而非重新導向

    def get_queryset(self):
        """僅允許使用者編輯自己的文章。"""
        return Post.objects.filter(author=self.request.user)

自訂權限

# permissions.py
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

角色型存取控制(RBAC)

# models.py
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。"""

    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)

SQL 注入防護

Django ORM 保護

# 良好:Django ORM 自動跳脫參數
def get_user(username):
    return User.objects.get(username=username)  # 安全

# 良好:對 raw() 使用參數
def search_users(query):
    return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])

# 錯誤:絕不要直接內插使用者輸入
def get_user_bad(username):
    return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # 有漏洞!

# 良好:使用 filter 並適當跳脫
def get_users_by_email(email):
    return User.objects.filter(email__iexact=email)  # 安全

# 良好:使用 Q 物件處理複雜查詢
from django.db.models import Q
def search_users_complex(query):
    return User.objects.filter(
        Q(username__icontains=query) |
        Q(email__icontains=query)
    )  # 安全

raw() 的額外安全措施

# 如果必須使用原始 SQL,務必使用參數
User.objects.raw(
    'SELECT * FROM users WHERE email = %s AND status = %s',
    [user_input_email, status]
)

XSS 防護

模板轉義

{# Django 預設會自動跳脫變數 - 安全 #}
{{ user_input }}  {# 跳脫後的 HTML #}

{# 僅對受信任的內容明確標記為安全 #}
{{ trusted_html|safe }}  {# 未跳脫 #}

{# 使用模板過濾器處理安全的 HTML #}
{{ user_input|escape }}  {# 與預設相同 #}
{{ user_input|striptags }}  {# 移除所有 HTML 標籤 #}

{# JavaScript 跳脫 #}
<script>
    var username = {{ username|escapejs }};
</script>

安全字串處理

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

# 錯誤:絕不將使用者輸入直接標記為安全而不跳脫
def render_bad(user_input):
    return mark_safe(user_input)  # 有漏洞!

# 良好:先跳脫,再標記為安全
def render_good(user_input):
    return mark_safe(escape(user_input))

# 良好:使用 format_html 處理包含變數的 HTML
from django.utils.html import format_html

def greet_user(username):
    return format_html('<span class="user">{}</span>', escape(username))

HTTP 標頭

# settings.py
SECURE_CONTENT_TYPE_NOSNIFF = True  # 防止 MIME 嗅探
SECURE_BROWSER_XSS_FILTER = True  # 啟用 XSS 過濾器
X_FRAME_OPTIONS = 'DENY'  # 防止點擊劫持

# 自訂中介層
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

CSRF 防護

預設 CSRF 保護

# settings.py - CSRF 預設啟用
CSRF_COOKIE_SECURE = True  # 僅透過 HTTPS 傳送
CSRF_COOKIE_HTTPONLY = True  # 防止 JavaScript 存取
CSRF_COOKIE_SAMESITE = 'Lax'  # 在某些情況下防止 CSRF
CSRF_TRUSTED_ORIGINS = ['https://example.com']  # 受信任的網域

# 模板使用
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">提交</button>
</form>

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

豁免檢視(請謹慎使用)

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # 僅在絕對必要時使用!
def webhook_view(request):
    # 來自外部服務的 Webhook
    pass

檔案上傳安全

檔案驗證

import os
import magic  # pip install python-magic
from django.core.exceptions import ValidationError

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """使用 magic bytes 驗證檔案類型,並交叉檢查副檔名。"""
    mime = magic.from_buffer(value.read(2048), mime=True)
    value.seek(0)

    if mime not in ALLOWED_MIMES:
        raise ValidationError('不支援的檔案類型。')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
        raise ValidationError('檔案副檔名與檔案內容不符。')

def validate_file_size(value):
    """驗證檔案大小(最大 5MB)。"""
    if value.size > 5 * 1024 * 1024:
        raise ValidationError('檔案過大。最大容量為 5MB。')

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

對於難以安裝 libmagic 的環境(例如最小化容器), 可以改用純 Python 的 filetype 套件作為替代方案:

import os
from django.core.exceptions import ValidationError

import filetype  # pip install filetype

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """使用 magic bytes 驗證檔案類型。"""
    kind = filetype.guess(value.read(2048))
    value.seek(0)

    if kind is None or kind.mime not in ALLOWED_MIMES:
        raise ValidationError('不支援的檔案類型。')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(kind.mime, set()):
        raise ValidationError('檔案副檔名與檔案內容不符。')

安全檔案儲存

# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'

# 在生產環境使用不同的網域存放媒體檔案
MEDIA_DOMAIN = 'https://media.example.com'

# 不要直接提供使用者上傳的檔案
# 對靜態檔案使用 whitenoise 或 CDN
# 對媒體檔案使用不同的伺服器或 S3

API 安全

速率限制

# 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',
    }
}

# 自訂節流器
from rest_framework.throttling import UserRateThrottle

class BurstRateThrottle(UserRateThrottle):
    scope = 'burst'
    rate = '60/min'

class SustainedRateThrottle(UserRateThrottle):
    scope = 'sustained'
    rate = '1000/day'

API 驗證

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

# 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 my_api_view(request):
    pass

安全部署

Nginx 設定

# /etc/nginx/sites-available/myapp
server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL 設定
    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # 安全標頭
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # 靜態檔案
    location /static/ {
        alias /var/www/static/;
        expires 30d;
        access_log off;
    }

    # 媒體檔案
    location /media/ {
        alias /var/www/media/;
        expires 30d;
    }

    # 代理到 Gunicorn
    location / {
        proxy_pass http://unix:/run/gunicorn.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect off;
    }
}

環境變數管理

# .env.production(僅作範例,切勿提交至版本控制)
DJANGO_SECRET_KEY=your-secure-secret-key-here
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://user:password@localhost:5432/dbname
REDIS_URL=redis://localhost:6379/0
[email protected]
EMAIL_HOST_PASSWORD=your-email-password

Docker 安全實踐

# Dockerfile.production
FROM python:3.11-slim

# 不要以 root 身分執行
RUN useradd -m -u 1000 appuser

# 安裝必要的系統套件
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 複製 requirements 並安裝(分層建構以利用快取)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 複製應用程式
COPY --chown=appuser:appuser . .

USER appuser

EXPOSE 8000

CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]

安全檢查清單

  • DEBUG 設為 False
  • SECRET_KEY 使用安全的隨機值
  • ALLOWED_HOSTS 已正確設定
  • HTTPS 已啟用且設定正確
  • 安全標頭已設定(HSTS, CSP, X-Frame-Options 等)
  • CSRF 保護已啟用
  • 密碼雜湊方式夠強
  • Session 已安全配置
  • 檔案上傳已驗證類型和大小
  • API 受到速率限制
  • 資料庫連線使用安全設定
  • 無法從用戶端存取敏感資訊
  • 所有套件皆為最新版本
  • 記錄與監控系統已就位
  • 定期安全備份

參考資源

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相關技能