DRF 認證機制:Token、JWT 與 Session 認證 | Django 教學

2026/06/15 2026/05/22
DRF 認證機制:Token、JWT 與 Session 認證 | Django 教學

你的 API 已經能正確處理 CRUD 操作了,但任何人都可以存取和修改資料 – 這在正式環境中是不可接受的。DRF 認證機制(Authentication)是 API 安全的第一道防線,它回答的核心問題是「你是誰?」。DRF 支援多種認證方式,從傳統的 Session AuthenticationToken Authentication,再到現代前後端分離架構首選的 JWT(JSON Web Token),每種方式都有其適用場景。本文將帶你逐一理解這些認證方案,並完整實作一個 JWT 登入流程。

認證(Authentication)vs 授權(Authorization)

在進入實作之前,必須先釐清兩個常被混淆的概念:

概念英文核心問題DRF 對應失敗回應
認證Authentication你是誰?authentication_classes401 Unauthorized
授權Authorization(Permission)你能做什麼?permission_classes403 Forbidden

DRF 的請求處理流程會依序執行三層安全檢查:

HTTP Request
     │
     ▼
┌─────────────────────────────────────┐
│  1. Authentication(認證)           │
│     識別用戶身份 → request.user      │
│     失敗 → 401 Unauthorized          │
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│  2. Permission(權限)               │
│     檢查用戶是否有權執行此操作        │
│     失敗 → 403 Forbidden             │
└──────────────────┬──────────────────┘
                   │
                   ▼
┌─────────────────────────────────────┐
│  3. Throttling(限流)               │
│     檢查請求頻率是否超過限制          │
│     失敗 → 429 Too Many Requests     │
└──────────────────┬──────────────────┘
                   │
                   ▼
            View 執行業務邏輯

本文專注於第一層 – Authentication,權限與限流將在下一篇文章詳細說明。


Session Authentication(Cookie-based)

Session Authentication 是 Django 最傳統的認證方式,依賴 Cookie 中的 Session ID 來識別用戶。

運作原理

1. 用戶透過表單登入(POST /login/)
2. Django 在伺服器端建立 Session,將 Session ID 寫入 Cookie
3. 瀏覽器後續請求自動帶上 Cookie
4. DRF 從 Cookie 中取得 Session ID,查詢資料庫找到對應用戶

設定方式

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
}

特點與限制

  • 優點:與 Django Admin 和 Browsable API 無縫整合,瀏覽器自動管理 Cookie
  • 缺點:需要 CSRF 保護(跨站請求偽造)、不適合行動 App 或跨域存取、Session 儲存在伺服器端增加負擔
  • 適用場景:Django 傳統的伺服器端渲染(SSR)應用、Browsable API 開發測試

Token Authentication(DRF 內建)

Token Authentication 是 DRF 內建的認證方式,為每個用戶產生一組唯一的 Token,儲存在資料庫中。

設定步驟

# settings.py
INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework.authtoken',  # 必須加入
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

執行 migrate 建立 Token 資料表:

python manage.py migrate

取得 Token(登入端點)

DRF 提供了內建的 ObtainAuthToken View,你也可以自訂回傳內容:

# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response

class CustomObtainAuthToken(ObtainAuthToken):
    """自訂登入端點,回傳更多用戶資訊"""

    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(
            data=request.data,
            context={'request': request},
        )
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        # get_or_create:若已有 Token 則回傳,否則建立新的
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'username': user.username,
            'email': user.email,
        })

# urls.py
from django.urls import path
from .views import CustomObtainAuthToken

urlpatterns = [
    path('api/auth/login/', CustomObtainAuthToken.as_view(), name='api-login'),
]

使用 Token 發送請求

# 1. 登入取得 Token
curl -X POST http://localhost:8000/api/auth/login/ \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "mypassword"}'

# 回應:{"token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b", ...}

# 2. 使用 Token 存取 API
curl http://localhost:8000/api/articles/ \
  -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"

Token Authentication 的限制

  • Token 不會自動過期,除非手動刪除
  • 每個用戶只有 一組 Token,無法區分不同裝置
  • 每次驗證都需要 查詢資料庫

JWT Authentication(djangorestframework-simplejwt)

JWT(JSON Web Token)是目前最流行的 API 認證方案,特別適合前後端分離的架構。它採用 Access Token + Refresh Token 的雙 Token 機制。

JWT 運作原理

JWT 雙 Token 機制
═══════════════════════════════════════════════════════

Access Token(短效期,如 30 分鐘)
  ● 用於存取 API
  ● 包含加密的用戶資訊(Payload)
  ● 伺服器不需查詢資料庫即可驗證(無狀態)
  ● 過期後需用 Refresh Token 換取新的

Refresh Token(長效期,如 7 天)
  ● 用於換取新的 Access Token
  ● 過期後需重新登入
  ● 可加入黑名單實現登出功能

安裝與設定

pip install djangorestframework-simplejwt
# settings.py
from datetime import timedelta

INSTALLED_APPS = [
    # ...
    'rest_framework',
    'rest_framework_simplejwt',
    'rest_framework_simplejwt.token_blacklist',  # 支援登出(可選)
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),    # Access Token 30 分鐘過期
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),        # Refresh Token 7 天過期
    'ROTATE_REFRESH_TOKENS': True,    # 每次刷新時產生新的 Refresh Token
    'BLACKLIST_AFTER_ROTATION': True,  # 舊的 Refresh Token 加入黑名單
    'ALGORITHM': 'HS256',
    'AUTH_HEADER_TYPES': ('Bearer',),  # Header 格式:Authorization: Bearer <token>
}

URL 配置

# urls.py
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
    TokenVerifyView,
    TokenBlacklistView,
)

urlpatterns = [
    # 登入:取得 Access Token + Refresh Token
    path('api/auth/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    # 刷新:用 Refresh Token 換取新的 Access Token
    path('api/auth/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
    # 驗證:檢查 Token 是否有效
    path('api/auth/token/verify/', TokenVerifyView.as_view(), name='token_verify'),
    # 登出:將 Refresh Token 加入黑名單
    path('api/auth/token/blacklist/', TokenBlacklistView.as_view(), name='token_blacklist'),
]

自訂 JWT Payload

預設的 JWT Payload 只包含 user_idexp(過期時間),你可以加入更多資訊:

# serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
    @classmethod
    def get_token(cls, user):
        token = super().get_token(user)
        # 在 Payload 中加入自訂欄位
        token['username'] = user.username
        token['email'] = user.email
        token['is_staff'] = user.is_staff
        return token
# settings.py
SIMPLE_JWT = {
    # ...
    'TOKEN_OBTAIN_SERIALIZER': 'myapp.serializers.CustomTokenObtainPairSerializer',
}

完整 JWT 登入流程範例

# 1. 登入取得 Token 對
curl -X POST http://localhost:8000/api/auth/token/ \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "mypassword"}'

# 回應:
# {
#   "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
#   "refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
# }

# 2. 使用 Access Token 存取 API
curl http://localhost:8000/api/articles/ \
  -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."

# 3. Access Token 過期後,用 Refresh Token 換取新的
curl -X POST http://localhost:8000/api/auth/token/refresh/ \
  -H "Content-Type: application/json" \
  -d '{"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}'

# 回應:
# {
#   "access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."(新的 Access Token)
# }

# 4. 登出:將 Refresh Token 加入黑名單
curl -X POST http://localhost:8000/api/auth/token/blacklist/ \
  -H "Content-Type: application/json" \
  -d '{"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."}'

OAuth2 概念簡介

OAuth2 是一個授權框架(Authorization Framework),主要用於第三方應用程式的授權場景(例如「使用 Google 帳號登入」)。

OAuth2 授權碼流程(Authorization Code Flow)
═══════════════════════════════════════════════════════

1. 用戶點擊「使用 Google 登入」
2. 瀏覽器導向 Google 授權頁面
3. 用戶同意授權
4. Google 將授權碼(Authorization Code)回傳給你的應用程式
5. 你的後端用授權碼向 Google 換取 Access Token
6. 用 Access Token 向 Google API 取得用戶資訊

在 Django 中可使用 django-oauth-toolkit 實作 OAuth2 Server,或用 django-allauth 整合第三方 OAuth2 登入。OAuth2 的完整實作較為複雜,多數專案使用 JWT 即可滿足需求。


認證方式比較表

認證方式狀態管理Token 過期資料庫查詢適用場景安全性
Session有狀態(伺服器端)Session 過期設定每次請求SSR 應用、Browsable API需 CSRF 保護
Basic無狀態每次請求測試環境低(密碼明文傳輸)
Token有狀態(資料庫)預設不過期每次請求簡單的 API 認證
JWT無狀態Access + Refresh不需要SPA、行動 App、微服務
OAuth2視實作而定可設定視實作而定第三方整合、SSO最高

DEFAULT_AUTHENTICATION_CLASSES 設定

DRF 支援同時設定多種認證方式,會按照列表順序逐一嘗試,直到有一個認證成功或全部失敗:

# settings.py - 全域設定
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        # 按順序嘗試,第一個成功的生效
        'rest_framework_simplejwt.authentication.JWTAuthentication',  # JWT 優先
        'rest_framework.authentication.SessionAuthentication',         # Session 次之
    ],
}

你也可以在個別 View 中覆寫全域設定:

# views.py
from rest_framework.views import APIView
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import AllowAny

class PublicArticleView(APIView):
    """公開端點:不需要認證"""
    authentication_classes = []       # 覆寫:不使用任何認證
    permission_classes = [AllowAny]

class AdminDashboardView(APIView):
    """管理端點:只接受 JWT 認證"""
    authentication_classes = [JWTAuthentication]  # 覆寫:只使用 JWT

自訂 Authentication Backend

當內建的認證方式無法滿足需求時,你可以繼承 BaseAuthentication 建立自己的認證類別:

# authentication.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from .models import APIKey

class APIKeyAuthentication(BaseAuthentication):
    """
    自訂 API Key 認證:
    從 X-API-Key Header 讀取 Key 進行驗證
    """

    def authenticate(self, request):
        """
        回傳 (user, auth) tuple 表示認證成功
        回傳 None 表示此認證方案不適用,DRF 會嘗試下一個
        拋出 AuthenticationFailed 表示認證失敗,直接回傳 401
        """
        api_key = request.META.get('HTTP_X_API_KEY')

        if not api_key:
            return None  # 沒有提供 API Key,交給下一個認證方案

        try:
            key_obj = APIKey.objects.select_related('user').get(
                key=api_key,
                is_active=True,
            )
        except APIKey.DoesNotExist:
            raise AuthenticationFailed('無效的 API Key。')

        if key_obj.is_expired():
            raise AuthenticationFailed('API Key 已過期。')

        return (key_obj.user, key_obj)  # (user, auth_info)

    def authenticate_header(self, request):
        """回傳 401 回應時的 WWW-Authenticate Header 值"""
        return 'X-API-Key'

將自訂認證加入設定:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'myapp.authentication.APIKeyAuthentication',  # 自訂認證
        'rest_framework.authentication.SessionAuthentication',
    ],
}

使用方式:

# 使用 API Key 存取 API
curl http://localhost:8000/api/articles/ \
  -H "X-API-Key: sk_abc123def456..."

總結

本文完整介紹了 DRF 的認證機制體系。Session Authentication 適合傳統的伺服器端渲染應用,Token Authentication 是 DRF 內建的簡單方案,而 JWT Authentication 則是現代前後端分離架構的首選,透過 Access Token + Refresh Token 的雙 Token 機制兼顧了安全性與使用體驗。

在實務中,多數新專案會選擇 JWT 作為主要認證方式,搭配 djangorestframework-simplejwt 可以快速完成設定。若有第三方登入需求,則可進一步整合 OAuth2

認證只是回答了「你是誰?」的問題,接下來還需要回答「你能做什麼?」– 這正是下一篇文章的主題:DRF 權限控制與 API 限流

BenZ Software Developer

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