Django Security 安全防護最佳實踐 | Django 教學

2026/06/27 2026/05/27
Django Security 安全防護最佳實踐 | Django 教學

安全性是 Web 應用開發中最不容妥協的環節。Django 作為「batteries included」的 Python Web 框架,內建了多層次的 Security(安全防護) 機制,能有效抵禦 CSRF(跨站請求偽造)XSS(跨站腳本攻擊)SQL Injection(SQL 注入攻擊)Clickjacking(點擊劫持) 等常見威脅。本篇將從 OWASP Top 10 對應的 Django 防護機制出發,逐一深入 CSRF、XSS、SQL Injection 的防護原理與實作細節,再涵蓋密碼雜湊策略、HTTPS 安全標頭配置,以及 manage.py check --deploy 安全檢查清單,幫助你全方位強化 Django 應用的安全防線。

OWASP Top 10 與 Django 的安全對應

OWASP(Open Web Application Security Project) 是國際知名的 Web 應用安全組織,其發布的 OWASP Top 10 列出了最常見的十大 Web 安全風險。Django 內建機制已覆蓋其中多項:

A01 Broken Access Control       → Django Permission 系統 + 物件層級權限
A02 Cryptographic Failures      → PBKDF2/Argon2 密碼雜湊、HTTPS 強制
A03 Injection                   → ORM 參數化查詢、模板自動轉義
A04 Insecure Design             → Django Security Checklist
A05 Security Misconfiguration   → python manage.py check --deploy
A06 Vulnerable Components       → pip audit / safety 套件掃描
A07 Auth & Session Failures     → SESSION_COOKIE_SECURE、HttpOnly
A08 Software Data Integrity     → SECRET_KEY、ALLOWED_HOSTS
A09 Logging & Monitoring        → Django Logging + Sentry 整合
A10 SSRF                        → 手動驗證外部 URL、網路隔離

Django 的 Security Middleware 堆疊(Middleware Stack) 是安全防護的第一道防線,標準配置如下:

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',          # HTTPS、HSTS、安全標頭
    'django.contrib.sessions.middleware.SessionMiddleware',   # Session 管理
    'django.middleware.csrf.CsrfViewMiddleware',              # CSRF 保護
    'django.middleware.clickjacking.XFrameOptionsMiddleware', # Clickjacking 防護
    # ... 其他 Middleware
]
威脅類型Django 防護機制運作方式
CSRFCsrfViewMiddleware + {% csrf_token %}每個 POST 表單自動驗證 Token
XSS模板引擎自動轉義<script> 轉為 &lt;script&gt;
SQL InjectionORM 參數化查詢所有 QuerySet 使用 prepared statement
ClickjackingXFrameOptionsMiddleware預設 X-Frame-Options: DENY

CSRF 防護機制

CSRF(Cross-Site Request Forgery,跨站請求偽造) 是一種攻擊手法:攻擊者誘導已登入的使用者在不知情的狀況下,向目標網站發送惡意請求(例如轉帳、修改密碼)。Django 透過 CsrfViewMiddleware 自動防護所有 POST、PUT、PATCH、DELETE 請求。

運作原理

  1. 使用者首次訪問網站時,Django 產生一組隨機的 CSRF Token 並存入 Cookie
  2. 表單頁面透過 {% csrf_token %} 將 Token 嵌入隱藏欄位
  3. 使用者提交表單時,Middleware 比對 Cookie 中的 Token 與表單中的 Token
  4. 若不一致,回傳 403 Forbidden
<!-- Django 模板中的表單 -->
<form method="POST" action="/transfer/">
    {% csrf_token %}  <!-- 自動產生隱藏欄位 -->
    <input type="text" name="amount" value="1000">
    <button type="submit">轉帳</button>
</form>

<!-- 渲染結果 -->
<form method="POST" action="/transfer/">
    <input type="hidden" name="csrfmiddlewaretoken"
           value="a1b2c3d4e5f6...(隨機 Token)">
    <input type="text" name="amount" value="1000">
    <button type="submit">轉帳</button>
</form>

AJAX 請求的 CSRF 處理

對於前後端分離架構,前端需要從 Cookie 讀取 CSRF Token,並在請求標頭中傳送:

// 從 Cookie 取得 CSRF Token
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let cookie of cookies) {
            cookie = cookie.trim();
            if (cookie.startsWith(name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

// 在 AJAX 請求中加入 CSRF Token
fetch('/api/transfer/', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRFToken': getCookie('csrftoken'),  // 標頭名稱
    },
    body: JSON.stringify({ amount: 1000 }),
});

@csrf_exempt 的風險

@csrf_exempt 裝飾器會跳過 CSRF 驗證,使用時必須極度謹慎:

from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie

# 危險!僅用於有其他認證機制保護的端點(如 JWT、API Key)
@csrf_exempt
def webhook_endpoint(request):
    """第三方 Webhook 回呼,使用簽名驗證取代 CSRF"""
    signature = request.META.get('HTTP_X_SIGNATURE')
    if not verify_webhook_signature(request.body, signature):
        return HttpResponseForbidden()
    # ... 處理 webhook 邏輯

# 強制回應包含 CSRF Cookie(SPA 前端首次載入時需要)
@ensure_csrf_cookie
def get_csrf_token(request):
    return JsonResponse({'status': 'ok'})

XSS 防護

XSS(Cross-Site Scripting,跨站腳本攻擊) 是指攻擊者將惡意 JavaScript 注入到網頁中,當其他使用者瀏覽該頁面時,惡意腳本會在受害者的瀏覽器中執行。Django 的模板引擎透過 Auto-escaping(自動轉義) 機制提供了強大的 XSS 防護。

模板自動轉義

Django 模板引擎預設會對所有變數輸出進行 HTML 轉義:

# views.py
def profile(request):
    # 假設使用者輸入了惡意內容
    context = {
        'username': '<script>alert("XSS")</script>',
    }
    return render(request, 'profile.html', context)
<!-- profile.html -->
<h1>歡迎,{{ username }}</h1>

<!-- 渲染結果(安全)-->
<h1>歡迎,&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;</h1>

Django 會將以下字元自動轉義:

原始字元轉義結果
<&lt;
>&gt;
&&amp;
"&quot;
'&#x27;

|safe 濾器與 mark_safe 的風險

當你確定某個變數的內容是安全的 HTML(例如從後台富文本編輯器產生的內容),可以使用 |safe 濾器關閉自動轉義,但這會帶來 XSS 風險:

<!-- 危險!如果 content 包含使用者輸入,可能導致 XSS -->
{{ user_content|safe }}

<!-- 更安全的做法:使用 bleach 套件過濾 HTML -->
{{ cleaned_content|safe }}
# views.py - 使用 bleach 清理使用者 HTML 輸入
import bleach

ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
ALLOWED_ATTRIBUTES = {'a': ['href', 'title']}

def clean_html(dirty_html):
    """清理使用者輸入的 HTML,只保留安全標籤"""
    return bleach.clean(
        dirty_html,
        tags=ALLOWED_TAGS,
        attributes=ALLOWED_ATTRIBUTES,
        strip=True,
    )

在 Python 程式碼中,mark_safe() 函式的風險同理:

from django.utils.safestring import mark_safe

# 危險!絕對不要對使用者輸入使用 mark_safe
dangerous = mark_safe(user_input)

# 安全用法:只對開發者自己撰寫的 HTML 使用
safe_html = mark_safe('<span class="badge">新</span>')

SQL Injection 防護

SQL Injection(SQL 注入攻擊) 是指攻擊者透過操控輸入資料,將惡意 SQL 語句注入到查詢中,進而讀取、修改或刪除資料庫資料。Django ORM 預設使用 參數化查詢(Parameterized Query),能有效防止 SQL Injection。

ORM 的安全保障

# 安全:Django ORM 自動使用參數化查詢
username = request.GET.get('username')
user = User.objects.filter(username=username)
# 實際 SQL:SELECT * FROM auth_user WHERE username = %s
# 參數:['輸入值']  ← 參數與 SQL 語句分離,無法注入

raw() 的安全與不安全用法

# 危險!使用字串格式化直接拼接 SQL
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'")

# 安全:使用參數化查詢
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [username])

extra() 與 RawSQL 的注意事項

# 危險:extra() 使用字串插值
User.objects.extra(where=[f"name = '{name}'"])

# 安全:extra() 使用 params 參數
User.objects.extra(where=["name = %s"], params=[name])

# 危險:RawSQL 直接插值
from django.db.models.expressions import RawSQL
queryset.annotate(val=RawSQL(f"select col from tbl where id={user_id}", ()))

# 安全:RawSQL 使用 params 參數
queryset.annotate(val=RawSQL("select col from tbl where id=%s", (user_id,)))

ORDER BY 白名單驗證

ORDER BY 子句無法使用參數化查詢(這是 SQL 本身的限制),因此必須手動進行白名單驗證:

ALLOWED_SORT_FIELDS = ['name', 'created_at', 'price', '-name', '-created_at', '-price']

sort_field = request.GET.get('sort', 'name')
if sort_field not in ALLOWED_SORT_FIELDS:
    sort_field = 'name'  # 回退到預設排序
queryset = Product.objects.order_by(sort_field)

Clickjacking 防護

Clickjacking(點擊劫持) 是一種攻擊手法:攻擊者將目標網站嵌入透明的 <iframe> 中,再疊加一個假的介面,誘導使用者在不知情的狀況下點擊目標網站的按鈕。Django 透過 XFrameOptionsMiddleware 設定 X-Frame-Options HTTP 標頭來防護:

# settings.py

# 完全禁止在 iframe 中嵌入(最安全,預設值)
X_FRAME_OPTIONS = 'DENY'

# 只允許同源的 iframe 嵌入
# X_FRAME_OPTIONS = 'SAMEORIGIN'

如果特定頁面需要被 iframe 嵌入(例如嵌入式小工具),可以使用裝飾器豁免:

from django.views.decorators.clickjacking import xframe_options_exempt

@xframe_options_exempt
def embeddable_widget(request):
    """允許被 iframe 嵌入的小工具頁面"""
    return render(request, 'widget.html')

密碼雜湊策略

Django 的認證系統不會以明文儲存密碼,而是使用 密碼雜湊(Password Hashing) 演算法將密碼轉為不可逆的雜湊值。Django 預設使用 PBKDF2,但也支援更安全的 bcryptArgon2

PASSWORD_HASHERS 設定

PASSWORD_HASHERS 設定決定了 Django 使用哪些密碼雜湊演算法,以及優先順序:

# settings.py

# 預設配置(PBKDF2)
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

# 推薦配置:將 Argon2 設為首選(需安裝 argon2-cffi)
# pip install argon2-cffi
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',     # 首選
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',     # 向後相容
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
演算法安裝需求安全性速度建議
PBKDF2內建良好中等Django 預設
bcryptpip install bcrypt優秀較慢廣泛使用
Argon2pip install argon2-cffi最佳可調推薦首選

列表中的第一個演算法用於新密碼的雜湊,其餘用於驗證舊密碼。當使用者登入時,如果密碼是用舊演算法雜湊的,Django 會自動升級為第一個演算法。

HTTPS 與安全標頭

在生產環境中,啟用 HTTPS 並配置安全標頭是保護通訊安全的基本要求。

完整生產安全設定

# settings/production.py

# === HTTPS 強制 ===
SECURE_SSL_REDIRECT = True                                     # HTTP 自動重導向至 HTTPS
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')  # 反向代理(如 Nginx)時使用

# === HSTS(HTTP Strict Transport Security)===
SECURE_HSTS_SECONDS = 31536000              # 瀏覽器記住 1 年內只用 HTTPS 存取
SECURE_HSTS_INCLUDE_SUBDOMAINS = True       # 包含所有子域名
SECURE_HSTS_PRELOAD = True                  # 提交至 HSTS preload list

# === Cookie 安全 ===
SESSION_COOKIE_SECURE = True                # Session Cookie 僅透過 HTTPS 傳送
SESSION_COOKIE_HTTPONLY = True              # 防止 JavaScript 讀取 Session Cookie
SESSION_COOKIE_SAMESITE = 'Lax'            # 防止跨站請求攜帶 Cookie
SESSION_COOKIE_AGE = 3600                   # Session 1 小時後過期

CSRF_COOKIE_SECURE = True                   # CSRF Cookie 僅透過 HTTPS 傳送
CSRF_COOKIE_SAMESITE = 'Lax'

# === 安全標頭 ===
SECURE_CONTENT_TYPE_NOSNIFF = True          # X-Content-Type-Options: nosniff
X_FRAME_OPTIONS = 'DENY'                   # 完全禁止 iframe 嵌入
SECURE_REFERRER_POLICY = 'strict-origin-when-cross-origin'  # Referrer-Policy

# === 應用層安全 ===
DEBUG = False                               # 生產環境絕對不能開啟 DEBUG
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']  # 從環境變數讀取,絕不 hardcode
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']

HSTS 注意事項

HSTS(HTTP Strict Transport Security) 告訴瀏覽器在指定時間內只能透過 HTTPS 存取該網站。一旦啟用,無法輕易撤銷(瀏覽器會記住),因此建議先用較短的 SECURE_HSTS_SECONDS(如 3600)測試,確認無誤後再延長至一年。

Content Security Policy

CSP(Content Security Policy,內容安全策略) 是一個額外的安全層,可以進一步防止 XSS 攻擊。透過 django-csp 套件可以輕鬆設定:

pip install django-csp
# settings.py
MIDDLEWARE = [
    # ... 其他 Middleware
    'csp.middleware.CSPMiddleware',
]

# CSP 設定:限制各類資源的載入來源
CSP_DEFAULT_SRC = ("'self'",)                                          # 預設只允許同源
CSP_SCRIPT_SRC = ("'self'", "https://cdn.jsdelivr.net")                # JavaScript 來源
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "https://fonts.googleapis.com")  # CSS 來源
CSP_IMG_SRC = ("'self'", "data:", "https:")                            # 圖片來源
CSP_FONT_SRC = ("'self'", "https://fonts.gstatic.com")                # 字型來源
CSP_FRAME_ANCESTORS = ("'none'",)                                      # 禁止被 iframe 嵌入

Rate Limiting 速率限制

Rate Limiting(速率限制) 能防止暴力破解攻擊和 API 濫用。使用 django-ratelimit 套件可以輕鬆實作:

pip install django-ratelimit
from django_ratelimit.decorators import ratelimit

# 限制每個 IP 每分鐘最多 10 次 POST 請求
@ratelimit(key='ip', rate='10/m', method='POST', block=True)
def login_view(request):
    # 超過限制會自動回傳 403
    ...

# 限制每個使用者每小時最多 100 次請求
@ratelimit(key='user', rate='100/h', block=False)
def api_endpoint(request):
    was_limited = getattr(request, 'limited', False)
    if was_limited:
        return JsonResponse({'error': '請求過於頻繁'}, status=429)
    ...

安全檢查清單:manage.py check –deploy

Django 內建了一個強大的安全檢查工具,能在部署前自動掃描設定中的安全隱患:

# 執行安全檢查
python manage.py check --deploy

# 常見輸出警告項目:
# WARNINGS:
# ?: (security.W004) 未設定 SECURE_HSTS_SECONDS
# ?: (security.W008) 未設定 SECURE_SSL_REDIRECT
# ?: (security.W009) SECRET_KEY 長度不足
# ?: (security.W012) SESSION_COOKIE_SECURE 為 False
# ?: (security.W016) CSRF_COOKIE_SECURE 為 False
# ?: (security.W018) DEBUG 設定為 True
# ?: (security.W019) X_FRAME_OPTIONS 未設定為 DENY

建議在 CI/CD 流程中加入此檢查:

# .github/workflows/deploy.yml
- name: Django Security Check
  run: python manage.py check --deploy --fail-level WARNING
  env:
    DJANGO_SETTINGS_MODULE: myproject.settings.production

完整安全檢查清單

項目設定說明
DEBUGFalse生產環境絕對不能為 True
SECRET_KEY環境變數至少 50 個字元,不 hardcode
ALLOWED_HOSTS明確列表不使用 ['*']
SECURE_SSL_REDIRECTTrue強制 HTTPS
SESSION_COOKIE_SECURETrueCookie 僅 HTTPS 傳送
CSRF_COOKIE_SECURETrueCSRF Cookie 僅 HTTPS 傳送
SECURE_HSTS_SECONDS31536000啟用 HSTS
X_FRAME_OPTIONS'DENY'防止 Clickjacking
SECURE_CONTENT_TYPE_NOSNIFFTrue防止 MIME 嗅探

SECRET_KEY 管理

SECRET_KEY 是 Django 安全機制的核心,用於 CSRF Token、Session 資料的加密簽章。洩漏 SECRET_KEY 會導致嚴重的安全問題。以下是幾種管理方式:

# 方法一:環境變數(最常見)
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')

# 方法二:python-decouple(推薦)
from decouple import config
SECRET_KEY = config('DJANGO_SECRET_KEY')

# 方法三:產生新的 SECRET_KEY
# python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

總結

Django 的安全防護體系可以說是 Python Web 框架中最完整的。從 CSRF 的 Token 驗證機制、XSS 的模板自動轉義、SQL Injection 的 ORM 參數化查詢,到 Clickjacking 的 X-Frame-Options 標頭,這些內建機制已經覆蓋了 OWASP Top 10 中的大部分威脅。在密碼儲存方面,建議將 Argon2 設為首選雜湊演算法;在生產環境中,務必啟用 HTTPS 強制重導向、HSTS、Secure Cookie 等安全標頭。最後,養成在部署前執行 python manage.py check --deploy 的習慣,並將其整合到 CI/CD 流程中,確保每次部署都經過安全性檢查。記住,安全不是一次性的工作,而是需要持續關注和更新的長期承諾。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。