فصل ۷: API و امکانات حرفه‌ای — DRF، کش، Celery و امنیت

DRF حرفه‌ای: permissions، pagination، throttling و JWT

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) نیاز دارد.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.