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 防護機制 | 運作方式 |
|---|---|---|
| CSRF | CsrfViewMiddleware + {% csrf_token %} | 每個 POST 表單自動驗證 Token |
| XSS | 模板引擎自動轉義 | <script> 轉為 <script> |
| SQL Injection | ORM 參數化查詢 | 所有 QuerySet 使用 prepared statement |
| Clickjacking | XFrameOptionsMiddleware | 預設 X-Frame-Options: DENY |
CSRF 防護機制
CSRF(Cross-Site Request Forgery,跨站請求偽造) 是一種攻擊手法:攻擊者誘導已登入的使用者在不知情的狀況下,向目標網站發送惡意請求(例如轉帳、修改密碼)。Django 透過 CsrfViewMiddleware 自動防護所有 POST、PUT、PATCH、DELETE 請求。
運作原理
- 使用者首次訪問網站時,Django 產生一組隨機的 CSRF Token 並存入 Cookie
- 表單頁面透過
{% csrf_token %}將 Token 嵌入隱藏欄位 - 使用者提交表單時,Middleware 比對 Cookie 中的 Token 與表單中的 Token
- 若不一致,回傳
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>歡迎,<script>alert("XSS")</script></h1>
Django 會將以下字元自動轉義:
| 原始字元 | 轉義結果 |
|---|---|
< | < |
> | > |
& | & |
" | " |
' | ' |
|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,但也支援更安全的 bcrypt 和 Argon2。
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 預設 |
| bcrypt | pip install bcrypt | 優秀 | 較慢 | 廣泛使用 |
| Argon2 | pip 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
完整安全檢查清單
| 項目 | 設定 | 說明 |
|---|---|---|
| DEBUG | False | 生產環境絕對不能為 True |
| SECRET_KEY | 環境變數 | 至少 50 個字元,不 hardcode |
| ALLOWED_HOSTS | 明確列表 | 不使用 ['*'] |
| SECURE_SSL_REDIRECT | True | 強制 HTTPS |
| SESSION_COOKIE_SECURE | True | Cookie 僅 HTTPS 傳送 |
| CSRF_COOKIE_SECURE | True | CSRF Cookie 僅 HTTPS 傳送 |
| SECURE_HSTS_SECONDS | 31536000 | 啟用 HSTS |
| X_FRAME_OPTIONS | 'DENY' | 防止 Clickjacking |
| SECURE_CONTENT_TYPE_NOSNIFF | True | 防止 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 流程中,確保每次部署都經過安全性檢查。記住,安全不是一次性的工作,而是需要持續關注和更新的長期承諾。