API عمومی بدون محافظ، دعوتنامه است
API برخلاف صفحهی HTML با اسکریپت فراخوانده میشود؛ یعنی مهاجم میتواند در یک دقیقه هزاران درخواست بفرستد. چهار لایهی پیشفرض را همیشه در تنظیمات سراسری DRF مشخص کنید تا هیچ endpointی بهطور تصادفی باز نماند.
# settings.py
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication", # اپ موبایل
"rest_framework.authentication.SessionAuthentication", # فرانت همدامنه
],
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
"DEFAULT_THROTTLE_CLASSES": [
"rest_framework.throttling.AnonRateThrottle",
"rest_framework.throttling.UserRateThrottle",
"rest_framework.throttling.ScopedRateThrottle",
],
"DEFAULT_THROTTLE_RATES": {"anon": "30/min", "user": "300/min", "otp": "5/hour"},
"NUM_PROXIES": 1, # پشت nginx: IP واقعی از X-Forwarded-For
}
permission سفارشی
# apps/api/permissions.py
from rest_framework.permissions import SAFE_METHODS, BasePermission
class IsOwnerOrStaffReadOnly(BasePermission):
message = "شما به این سفارش دسترسی ندارید."
def has_permission(self, request, view):
return request.user.is_authenticated
def has_object_permission(self, request, view, obj):
if request.user.is_staff:
return request.method in SAFE_METHODS
return obj.customer.user_id == request.user.id
class OrderViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrStaffReadOnly]
class OTPRequestView(APIView):
permission_classes = [AllowAny]
throttle_scope = "otp" # فقط ۵ درخواست در ساعت
صفحهبندی
| کلاس | پارامتر | مناسب برای |
|---|---|---|
| PageNumberPagination | ?page=3 | پنلها و فهرستهای معمولی |
| LimitOffsetPagination | ?limit=50&offset=100 | جدولهای با اندازهی صفحهی متغیر |
| CursorPagination | ?cursor=… | جدولهای بسیار بزرگ و اسکرول بینهایت اپ |
JWT برای اپ موبایل
# pip install djangorestframework-simplejwt
from datetime import timedelta
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=15),
"REFRESH_TOKEN_LIFETIME": timedelta(days=14),
"ROTATE_REFRESH_TOKENS": True,
"BLACKLIST_AFTER_ROTATION": True, # نیاز به اپ token_blacklist
}
urlpatterns += [
path("auth/token/", TokenObtainPairView.as_view()),
path("auth/token/refresh/", TokenRefreshView.as_view()),
]
برای ورود با OTP، بهجای TokenObtainPairView یک View بنویسید که کد را با verify_otp درس ۳۰ بررسی کند و با RefreshToken.for_user(user) توکن بسازد.
نکتههایی که کمتر کسی میداند
has_object_permissionفقط وقتی اجرا میشود که View متدget_object()را صدا بزند؛ در list هرگز اجرا نمیشود. محدودیت فهرست را حتماً درget_querysetبگذارید.- throttling از کش استفاده میکند؛ با LocMemCache و چند worker هر worker شمارندهی جدا دارد و محدودیت واقعی چند برابر میشود. Redis لازم است.
- بدون
NUM_PROXIESپشت nginx همهی کاربران IP یکسان (127.0.0.1) دارند و یک کاربر پرکار سهمیهی anon همه را تمام میکند. - JWT پس از صدور قابل باطلکردن نیست تا منقضی شود؛ برای همین access کوتاه (۵ تا ۱۵ دقیقه) و refresh با rotation و blacklist انتخاب امن است.
- PageNumberPagination در صفحات دور (
?page=5000) کند میشود چون OFFSET بزرگ میسازد؛ CursorPagination زمان ثابت دارد اما به یک ترتیب یکتا و ثابت (مثلاً-created_at) نیاز دارد.