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
四種內建權限的比較:
| 權限類別 | 匿名讀取 | 匿名寫入 | 認證讀取 | 認證寫入 | 管理員 |
|---|---|---|---|---|---|
| AllowAny | O | O | O | O | O |
| IsAuthenticated | X | X | O | O | O |
| IsAdminUser | X | X | X | X | O |
| IsAuthenticatedOrReadOnly | O | X | O | O | O |
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 內建的權限系統(add、change、delete、view):
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 設定
頻率格式為 次數/時間單位,支援的時間單位:
| 縮寫 | 時間單位 | 範例 |
|---|---|---|
second | 秒 | 5/second |
minute | 分鐘 | 30/minute |
hour | 小時 | 1000/hour |
day | 天 | 5000/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 內建了 AllowAny、IsAuthenticated、IsAdminUser 和 IsAuthenticatedOrReadOnly 四種常用權限類別,並支援透過 BasePermission 自訂權限邏輯。has_permission 負責全域權限檢查,has_object_permission 負責物件層級權限檢查,兩者各有其觸發時機。DjangoModelPermissions 可以直接對應 Django 的內建權限系統,適合需要細粒度權限管理的專案。
在限流方面,AnonRateThrottle 和 UserRateThrottle 分別控制匿名和認證用戶的請求頻率,ScopedRateThrottle 則可以為不同端點設定不同的限流策略。自訂 Throttle 類別還能根據用戶等級動態調整限流額度。
至此,我們已經完整學會了 DRF 的三層安全機制:認證(你是誰)、權限(你能做什麼)、限流(你能做多少次)。將這三者結合起來,就能建構出一個安全、穩定且可控的 API 服務。