DRF 權限控制與 API 限流完全指南 | Django 教學

2026/06/16 2026/05/26
DRF 權限控制與 API 限流完全指南 | Django 教學

上一篇文章解決了「你是誰?」的認證問題,但光知道用戶身份還不夠 – 我們還需要控制「你能做什麼?」以及「你能做多少次?」。DRF 的 Permissions 權限機制決定了已認證的用戶可以執行哪些操作,而 Throttling 限流機制則防止 API 被濫用或遭受暴力攻擊。這三層安全機制(Authentication → Permission → Throttle)環環相扣,共同構成了一個完整的 API 安全防護體系。

權限系統概覽

DRF 的每個 HTTP 請求在進入 View 的業務邏輯之前,都會依序通過三層安全檢查:

HTTP Request
     │
     ▼
  Authentication(認證)→ 你是誰?         → 失敗:401 Unauthorized
     │
     ▼
  Permission(權限)   → 你能做什麼?      → 失敗:403 Forbidden
     │
     ▼
  Throttling(限流)   → 你能做多少次?    → 失敗:429 Too Many Requests
     │
     ▼
  View 業務邏輯

Permission 層的特點:

  • 認證之後 執行,此時 request.user 已經確定
  • 多個 Permission 之間是 AND 邏輯,全部通過才允許
  • 分為 全域權限has_permission)和 物件層級權限has_object_permission

內建權限類別

DRF 提供了四個常用的內建 Permission 類別:

AllowAny

from rest_framework.permissions import AllowAny

class PublicArticleView(generics.ListAPIView):
    """任何人都可以存取,包含未認證的匿名用戶"""
    permission_classes = [AllowAny]
    queryset = Article.objects.filter(is_published=True)
    serializer_class = ArticleSerializer

IsAuthenticated

from rest_framework.permissions import IsAuthenticated

class MyArticleView(generics.ListCreateAPIView):
    """只有已登入的用戶可以存取"""
    permission_classes = [IsAuthenticated]
    serializer_class = ArticleSerializer

    def get_queryset(self):
        return Article.objects.filter(author=self.request.user)

IsAdminUser

from rest_framework.permissions import IsAdminUser

class AdminDashboardView(APIView):
    """只有 is_staff=True 的管理員可以存取"""
    permission_classes = [IsAdminUser]

    def get(self, request):
        stats = {
            'total_articles': Article.objects.count(),
            'total_users': User.objects.count(),
        }
        return Response(stats)

IsAuthenticatedOrReadOnly

from rest_framework.permissions import IsAuthenticatedOrReadOnly

class ArticleViewSet(viewsets.ModelViewSet):
    """
    未登入用戶只能讀取(GET、HEAD、OPTIONS)
    已登入用戶可以執行所有操作(包含 POST、PUT、DELETE)
    """
    permission_classes = [IsAuthenticatedOrReadOnly]
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

四種內建權限的比較:

權限類別匿名讀取匿名寫入認證讀取認證寫入管理員
AllowAnyOOOOO
IsAuthenticatedXXOOO
IsAdminUserXXXXO
IsAuthenticatedOrReadOnlyOXOOO

permission_classes 設定

Permission 可以在兩個層級設定:全域和 View 層級。

全域設定

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',  # 預設所有 API 都需要認證
    ],
}

View 層級覆寫

from rest_framework.permissions import AllowAny, IsAuthenticated, IsAdminUser

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer

    def get_permissions(self):
        """根據不同的 action 動態設定權限"""
        if self.action == 'list':
            # 列表:任何人都可以看
            return [AllowAny()]
        elif self.action == 'create':
            # 建立:需要登入
            return [IsAuthenticated()]
        elif self.action in ('update', 'partial_update', 'destroy'):
            # 修改/刪除:需要是作者(自訂權限,下節說明)
            return [IsAuthenticated(), IsAuthorOrReadOnly()]
        return [IsAuthenticated()]

自訂 Permission 類別

當內建權限不足以滿足需求時,你可以繼承 BasePermission 建立自訂權限。

has_permission vs has_object_permission

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAuthorOrReadOnly(BasePermission):
    """
    物件級權限:
    - 任何人可以讀取(GET、HEAD、OPTIONS)
    - 只有作者可以修改或刪除
    """
    # 權限被拒絕時的錯誤訊息
    message = '只有文章作者才能執行此操作。'

    def has_permission(self, request, view):
        """
        全域權限檢查(每次請求都會執行)
        - 安全方法(GET、HEAD、OPTIONS):允許所有人
        - 寫入方法(POST、PUT、DELETE):需要已認證
        """
        if request.method in SAFE_METHODS:
            return True
        return request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        """
        物件級權限檢查(只有在 get_object() 時執行)
        - 安全方法:允許所有人
        - 寫入方法:只有作者允許
        """
        if request.method in SAFE_METHODS:
            return True
        return obj.author == request.user

在 ViewSet 中使用:

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]
    # AND 邏輯:用戶必須已認證 AND 是作者才能修改

更多自訂權限範例

class IsAdminOrReadOnly(BasePermission):
    """只有管理員可以寫入,所有人可以讀取"""

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user and request.user.is_staff


class IsPremiumUser(BasePermission):
    """只有付費用戶可以存取"""
    message = '此功能僅限付費用戶使用。'

    def has_permission(self, request, view):
        return (
            request.user and
            request.user.is_authenticated and
            hasattr(request.user, 'profile') and
            request.user.profile.is_premium
        )


class IsOwnerOrAdmin(BasePermission):
    """物件擁有者或管理員可以操作"""

    def has_object_permission(self, request, view, obj):
        # 管理員可以操作任何物件
        if request.user.is_staff:
            return True
        # 一般用戶只能操作自己的物件
        return obj.owner == request.user

Object-Level Permissions(物件層級權限)

物件層級權限在 get_object() 被呼叫時自動觸發。需要注意的是,列表操作(list)不會觸發物件層級權限,因為 list 不會呼叫 get_object()

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated, IsAuthorOrReadOnly]

    def get_queryset(self):
        """
        列表操作的權限控制:透過過濾 queryset 實現
        管理員可以看到所有文章,一般用戶只能看到公開文章和自己的文章
        """
        user = self.request.user
        if user.is_staff:
            return Article.objects.all()

        from django.db.models import Q
        return Article.objects.filter(
            Q(is_published=True) | Q(author=user)
        )

物件層級權限的執行時機:

操作呼叫 get_object()觸發 has_object_permission
list否(需透過 queryset 過濾)
create
retrieve
update / partial_update
destroy

DjangoModelPermissions

DRF 提供了 DjangoModelPermissions,可以直接對應 Django 內建的權限系統(addchangedeleteview):

from rest_framework.permissions import DjangoModelPermissions

class ArticleViewSet(viewsets.ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [DjangoModelPermissions]
    # 自動對應:
    # POST   → 需要 myapp.add_article 權限
    # PUT    → 需要 myapp.change_article 權限
    # DELETE → 需要 myapp.delete_article 權限
    # GET    → 預設允許已認證用戶(可用 DjangoModelPermissionsOrAnonReadOnly 允許匿名讀取)

這在使用 Django Admin 管理用戶權限的專案中特別有用,因為你可以在 Admin 介面中直接為用戶或群組指派權限。


Throttling 限流機制

Throttling 限制了用戶在一段時間內可以發送的請求次數,防止 API 被濫用或遭受暴力攻擊。

內建 Throttle 類別

DRF 提供了三種內建的 Throttle 類別:

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',   # 匿名用戶限流
        'rest_framework.throttling.UserRateThrottle',   # 認證用戶限流
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',     # 匿名用戶:每天 100 次
        'user': '1000/day',   # 認證用戶:每天 1000 次
    }
}

AnonRateThrottle:根據 IP 地址識別匿名用戶

# 匿名用戶每小時最多 60 次請求
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'anon': '60/hour',
    }
}

UserRateThrottle:根據用戶 ID 識別已認證用戶

# 認證用戶每天最多 5000 次請求
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'user': '5000/day',
    }
}

ScopedRateThrottle:為不同的 View 設定不同的限流範圍

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.ScopedRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'login': '5/minute',       # 登入端點:每分鐘 5 次
        'upload': '20/hour',       # 上傳端點:每小時 20 次
        'contact': '3/day',        # 聯絡表單:每天 3 次
    }
}
# views.py
class LoginView(APIView):
    throttle_scope = 'login'  # 使用 'login' 限流範圍

    def post(self, request):
        # 登入邏輯
        pass

class FileUploadView(APIView):
    throttle_scope = 'upload'  # 使用 'upload' 限流範圍

    def post(self, request):
        # 上傳邏輯
        pass

DEFAULT_THROTTLE_RATES 設定

頻率格式為 次數/時間單位,支援的時間單位:

縮寫時間單位範例
second5/second
minute分鐘30/minute
hour小時1000/hour
day5000/day
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'anon': '60/hour',          # 匿名用戶:每小時 60 次
        'user': '5000/day',         # 認證用戶:每天 5000 次
        'login': '5/minute',        # 登入端點:每分鐘 5 次
        'premium': '10000/day',     # 付費用戶:每天 10000 次
    }
}

自訂 Throttle 類別

根據用戶等級設定不同限流

# throttles.py
from rest_framework.throttling import UserRateThrottle

class PremiumUserThrottle(UserRateThrottle):
    """付費用戶使用更高的限流額度"""
    scope = 'premium'

    def allow_request(self, request, view):
        # 管理員不限流
        if request.user and request.user.is_staff:
            return True

        # 付費用戶使用 'premium' scope 的限流設定
        if (request.user and
            hasattr(request.user, 'profile') and
            request.user.profile.is_premium):
            self.scope = 'premium'
            self.rate = self.get_rate()
            self.num_requests, self.duration = self.parse_rate(self.rate)

        return super().allow_request(request, view)

登入端點專用限流

# throttles.py
from rest_framework.throttling import AnonRateThrottle

class LoginRateThrottle(AnonRateThrottle):
    """登入端點限流,防止暴力破解"""
    scope = 'login'

# views.py
class LoginView(APIView):
    permission_classes = [AllowAny]
    throttle_classes = [LoginRateThrottle]

    def post(self, request):
        # 登入邏輯
        pass

完整範例:結合認證、權限、限流的 API

以下是一個完整的範例,展示如何將認證、權限與限流三者結合:

設定檔

# settings.py
from datetime import timedelta

REST_FRAMEWORK = {
    # 認證
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework_simplejwt.authentication.JWTAuthentication',
        'rest_framework.authentication.SessionAuthentication',
    ],
    # 權限
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    # 限流
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '60/hour',
        'user': '5000/day',
        'login': '5/minute',
    },
}

SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(minutes=30),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
    'ROTATE_REFRESH_TOKENS': True,
    'BLACKLIST_AFTER_ROTATION': True,
}

自訂權限

# permissions.py
from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAuthorOrReadOnly(BasePermission):
    """只有作者可以修改,其他人只能讀取"""
    message = '只有作者才能修改此內容。'

    def has_permission(self, request, view):
        if request.method in SAFE_METHODS:
            return True
        return request.user and request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        if request.method in SAFE_METHODS:
            return True
        return obj.author == request.user

自訂限流

# throttles.py
from rest_framework.throttling import AnonRateThrottle

class LoginRateThrottle(AnonRateThrottle):
    scope = 'login'

ViewSet 與 URL 配置

# views.py
from rest_framework import viewsets, generics
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_simplejwt.views import TokenObtainPairView

from .models import Article
from .serializers import ArticleSerializer
from .permissions import IsAuthorOrReadOnly
from .throttles import LoginRateThrottle

class ThrottledTokenObtainPairView(TokenObtainPairView):
    """加上限流的登入端點"""
    throttle_classes = [LoginRateThrottle]

class ArticleViewSet(viewsets.ModelViewSet):
    """
    文章 CRUD API
    - 列表/詳情:任何人可讀取
    - 建立:需要登入
    - 修改/刪除:只有作者可操作
    """
    queryset = Article.objects.select_related('author').order_by('-created_at')
    serializer_class = ArticleSerializer

    def get_permissions(self):
        if self.action in ('list', 'retrieve'):
            return [AllowAny()]
        return [IsAuthenticated(), IsAuthorOrReadOnly()]

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)


class UserProfileView(APIView):
    """用戶個人資料(需要認證)"""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({
            'id': request.user.id,
            'username': request.user.username,
            'email': request.user.email,
        })
# urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import (
    ThrottledTokenObtainPairView,
    ArticleViewSet,
    UserProfileView,
)
from rest_framework_simplejwt.views import TokenRefreshView

router = DefaultRouter()
router.register('articles', ArticleViewSet, basename='article')

urlpatterns = [
    # 認證端點
    path('api/auth/token/', ThrottledTokenObtainPairView.as_view()),
    path('api/auth/token/refresh/', TokenRefreshView.as_view()),

    # 用戶端點
    path('api/profile/', UserProfileView.as_view()),

    # 資源端點(Router 自動產生)
    path('api/', include(router.urls)),
]

測試流程

# 1. 匿名用戶讀取文章列表(允許)
curl http://localhost:8000/api/articles/

# 2. 匿名用戶嘗試建立文章(被拒:401)
curl -X POST http://localhost:8000/api/articles/ \
  -H "Content-Type: application/json" \
  -d '{"title": "測試", "content": "內容"}'
# 回應:{"detail": "Authentication credentials were not provided."}

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

# 4. 使用 Token 建立文章(成功)
curl -X POST http://localhost:8000/api/articles/ \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -d '{"title": "我的文章", "content": "文章內容"}'

# 5. 其他用戶嘗試修改(被拒:403)
curl -X PUT http://localhost:8000/api/articles/1/ \
  -H "Authorization: Bearer eyJ...(其他用戶的 Token)" \
  -H "Content-Type: application/json" \
  -d '{"title": "修改標題"}'
# 回應:{"detail": "只有作者才能修改此內容。"}

# 6. 頻繁登入測試限流(超過 5 次/分鐘後被拒:429)
# 回應:{"detail": "Request was throttled. Expected available in 45 seconds."}

總結

本文完整介紹了 DRF 安全機制的後兩層 – Permissions 權限控制與 Throttling 限流。

在權限方面,DRF 內建了 AllowAnyIsAuthenticatedIsAdminUserIsAuthenticatedOrReadOnly 四種常用權限類別,並支援透過 BasePermission 自訂權限邏輯。has_permission 負責全域權限檢查,has_object_permission 負責物件層級權限檢查,兩者各有其觸發時機。DjangoModelPermissions 可以直接對應 Django 的內建權限系統,適合需要細粒度權限管理的專案。

在限流方面,AnonRateThrottleUserRateThrottle 分別控制匿名和認證用戶的請求頻率,ScopedRateThrottle 則可以為不同端點設定不同的限流策略。自訂 Throttle 類別還能根據用戶等級動態調整限流額度。

至此,我們已經完整學會了 DRF 的三層安全機制:認證(你是誰)、權限(你能做什麼)、限流(你能做多少次)。將這三者結合起來,就能建構出一個安全、穩定且可控的 API 服務。

BenZ Software Developer

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