تا الان چندین API ساختید. مقالات را می‌توانید ببینید، بسازید، ویرایش کنید و حذف نمایید.

اما یک مشکل بزرگ وجود دارد.

همه این کارها را هر کسی می‌تواند انجام دهد. حتی کاربری که لاگین نکرده. حتی کسی که هیچ ارتباطی با سایت شما ندارد.

در دنیای واقعی، اینطور نیست.

یک مقاله را فقط نویسنده آن باید بتواند ویرایش کند. یک محصول را فقط ادمین باید بتواند حذف کند. یک پست خصوصی را فقط خود کاربر باید ببیند.

اینجا دو مفهوم وارد می‌شود: احراز هویت و مجوزها.

احراز هویت یعنی تشخیص هویت کاربر. ببینیم چه کسی وارد شده. توکن دارد یا نه. لاگین کرده یا نه.

مجوزها یعنی بعد از اینکه فهمیدیم کاربر کیست، مشخص کنیم چه کاری مجاز است انجام دهد. آیا اجازه حذف این مقاله را دارد؟ آیا می‌تواند این محصول را ببیند؟

در این درس، با انواع روش‌های احراز هویت در DRF آشنا می‌شوید. از BasicAuthentication ساده تا TokenAuthentication که استاندارد اصلی در APIهاست.

همچنین یاد می‌گیرید چطور دسترسی‌ها را با IsAuthenticated، IsAdminUser و AllowAny مدیریت کنید.

در انتها، DjangoObjectPermissions را می‌بینید که اجازه می‌دهد دسترسی را روی هر شیء به صورت جداگانه کنترل کنید. و یاد می‌گیرید چطور مجوزهای سفارشی برای نیازهای خاص خودتان بسازید.

این درس برای ورود به بازار کار بسیار مهم است. چون در هیچ پروژه جدی‌ای، همه کاربران به همه چیز دسترسی ندارند.

برای این درس، به مدل Article خود یک فیلد author اضافه می‌کنیم تا مالک هر مقاله را مشخص کنیم.

احراز هویت چیست؟ 

قبل از اینکه کاربر بتواند کاری انجام دهد، باید بدانیم او کیست. به این فرآیند می‌گویند احراز هویت یا Authentication.

DRF سه روش اصلی برای احراز هویت دارد. هر کدام برای شرایط خاصی مناسب است.

Basic Authentication

پساده‌ترین روش احراز هویت.

کاربر هر بار درخواست می‌فرستد، نام کاربری و رمز عبور را در هدر درخواست قرار می‌دهد. این اطلاعات به صورت متن ساده فرستاده می‌شود.

مزایا:

  • پیاده‌سازی بسیار ساده
  • پشتیبانی همه مرورگرها و ابزارها
  • مناسب برای تست و محیط توسعه

معایب:

  • امنیت پایین (رمز در هر درخواست فرستاده می‌شود)
  • حتماً باید از HTTPS استفاده کنید
  • کاربر هر بار باید رمز را وارد کند

چه موقع استفاده کنیم؟

  • محیط توسعه و تست
  • APIهای داخلی با ترافیک کم
  • وقتی HTTPS دارید و سادگی اولویت است

Session Authentication

از همان سیستم احراز هویت جنگو استفاده می‌کند. کاربر یک بار لاگین می‌کند، یک کوکی (Cookie) در مرورگر ذخیره می‌شود. درخواست‌های بعدی خودکار این کوکی را می‌فرستند.

مزایا:

  • یکپارچگی با پنل ادمین جنگو
  • کاربر فقط یک بار لاگین می‌کند
  • امنیت مناسب (رمز در هر درخواست فرستاده نمی‌شود)

معایب:

  • فقط برای مرورگر کار می‌کند
  • برای اپلیکیشن موبایل یا برنامه‌های مستقل مناسب نیست
  • نیاز به مدیریت کوکی و CSRF

چه موقع استفاده کنیم؟

وقتی فرانت‌اند و بک‌اند روی یک دامنه هستند

سایت‌هایی که هم صفحات HTML و هم API دارند

پروژه‌هایی که از پنل ادمین جنگو استفاده می‌کنند

Token Authentication

استاندارد مدرن احراز هویت برای APIها.

کاربر یک بار نام کاربری و رمز می‌فرستد. سرور یک توکن یکتا تولید می‌کند و برمی‌گرداند. کاربر توکن را در هدر درخواست‌های بعدی قرار می‌دهد.

مزایا:

  • مستقل از مرورگر (هر کلاینت می‌تواند استفاده کند)
  • بدون نیاز به کوکی و CSRF
  • مناسب برای اپلیکیشن موبایل، برنامه‌های دسکتاپ، و فرانت‌اندهای جداگانه
  • می‌توان توکن را هر زمان باطل کرد

معایب:

  • نیاز به ذخیره توکن در سمت کلاینت
  • توکن تا زمان انقضا معتبر است (مگر اینکه دستی باطل شود)
  • پیاده‌سازی کمی پیچیده‌تر از Basic

چه موقع استفاده کنیم؟

  • اکثر APIهای واقعی (انتخاب اصلی)
  • اپلیکیشن‌های موبایل و SPA (React, Vue, Angular)
  • زمانی که فرانت‌اند و بک‌اند روی دامنه‌های جداگانه هستند

 

جدول مقایسه سه روش

ویژگی Basic Session Token
سطح امنیت کم (بدون HTTPS) متوسط بالا
نیاز به HTTPS اجباری توصیه می‌شود توصیه می‌شود
مناسب برای مرورگر بله بله بله
مناسب برای موبایل بله (با ذخیره رمز) خیر بله
یکپارچگی با ادمین جنگو خیر بله خیر
پیچیدگی پیاده‌سازی خیلی کم کم متوسط
نیاز به ذخیره وضعیت در سرور خیر بله خیر

تنظیم در DRF

می‌توانید یک یا چند روش را همزمان فعال کنید:

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

ترتیب مهم است. DRF از بالا به پایین چک می‌کند. اولین روشی که موفق شود، احراز هویت را انجام می‌دهد.

کدام را انتخاب کنیم؟

برای اکثر پروژه‌های امروزی، TokenAuthentication بهترین گزینه است.

اگر فقط API می‌نویسید و فرانت‌اند جداگانه دارید → Token

اگر سایت معمولی با صفحات HTML دارید و API هم دارید → Session + Token

اگر در محیط توسعه و تست هستید → Basic یا Token

اگر اپلیکیشن موبایل دارید → Token

TokenAuthentication (نحوه لاگین و دریافت توکن)

در میان روش‌های احراز هویت DRF، TokenAuthentication محبوب‌ترین گزینه برای APIهای مدرن است. این روش مستقل از مرورگر کار می‌کند و برای اپلیکیشن‌های موبایل، فرانت‌اندهای جداگانه (React, Vue, Angular)، و هر کلاینت دیگری که بتواند هدر HTTP بفرستد، مناسب است.

قدم اول: فعال کردن TokenAuthentication

برای استفاده از TokenAuthentication، دو تغییر در settings.py باید اعمال کنید.

1. اضافه کردن اپلیکیشن authtoken:

INSTALLED_APPS = [
    ...
    'rest_framework',
    'rest_framework.authtoken',  # اضافه شود
    ...
]

2. اضافه کردن به DEFAULT_AUTHENTICATION_CLASSES:

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

بعد از این تغییرات، یک بار دستور python manage.py migrate را اجرا کنید. این دستور جداول مورد نیاز برای ذخیره توکن‌ها را در دیتابیس می‌سازد.

قدم دوم: ساخت توکن برای کاربران

حالا که تنظیمات انجام شد، باید برای هر کاربر یک توکن بسازید. چند روش برای این کار وجود دارد.

روش اول: ساخت خودکار با سیگنال (پیشنهادی)

با این روش، به محض ساخته شدن هر کاربر جدید، توکن او هم ساخته می‌شود. کد زیر را در فایل models.py هر اپلیکیشنی قرار دهید:

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token

@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        Token.objects.create(user=instance)

روش دوم: ساخت با دستور مدیریتی

برای کاربرانی که از قبل وجود دارند، می‌توانید با این دستور توکن بسازید:

python manage.py drf_create_token username

روش سوم: ساخت دستی در محیط پایتون

from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User

user = User.objects.get(username='ali')
token, created = Token.objects.get_or_create(user=user)
print(token.key)

قدم سوم: ساخت اندپوینت لاگین (دریافت توکن)

بیشتر اوقات، نمی‌خواهید توکن را دستی بسازید. می‌خواهید کاربر نام کاربری و رمز عبورش را بفرستد و توکن را دریافت کند. DRF یک ویو آماده برای این کار دارد.

در فایل urls.py اصلی پروژه، این مسیر را اضافه کنید:

from rest_framework.authtoken import views

urlpatterns = [
    path('api-token-auth/', views.obtain_auth_token),
]

حالا کاربر می‌تواند با ارسال درخواست POST به آدرس http://127.0.0.1:8000/api-token-auth/، توکن خود را دریافت کند.

بدنه درخواست (JSON):

{
    "username": "ali",
    "password": "mysecretpassword"
}

پاسخ سرور:

{
    "token": "9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b"
}

قدم چهارم: سفارشی‌سازی ویو دریافت توکن

ویو پیش‌فرض obtain_auth_token فقط توکن را برمی‌گرداند. اگر نیاز دارید اطلاعات بیشتری مثل user_id یا email هم برگردد، می‌توانید آن را سفارشی کنید:

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

class CustomAuthToken(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']
        token, created = Token.objects.get_or_create(user=user)
        return Response({
            'token': token.key,
            'user_id': user.pk,
            'email': user.email
        })

قدم پنجم: استفاده از توکن در درخواست‌ها

کلاینت (مثل فرانت‌اند یا اپلیکیشن موبایل) باید توکن را در هدر Authorization هر درخواست ارسال کند.

قالب هدر:

Authorization: Token <token_key>

مثال با curl:

curl -X GET http://127.0.0.1:8000/api/articles/ \
     -H 'Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'

مثال در کد پایتون (با requests):

import requests

url = 'http://127.0.0.1:8000/api/articles/'
headers = {'Authorization': 'Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b'}
response = requests.get(url, headers=headers)

قدم ششم: استفاده در ویوهای DRF

در سمت سرور، کافی است authentication_classes ویو را تنظیم کنید. DRF خودکار توکن را بررسی می‌کند و request.user را پر می‌کند:

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated

class ArticleList(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        # request.user الان کاربر احراز هویت شده است
        content = {'message': f'Hello {request.user.username}'}
        return Response(content)

نکات امنیتی مهم

۱. همیشه از HTTPS استفاده کنید

اگر از TokenAuthentication در محیط تولید (production) استفاده می‌کنید، حتماً API شما فقط از طریق HTTPS در دسترس باشد. در غیر این صورت، توکن‌ها در مسیر شبکه قابل رصد هستند.

۲. توکن را در سمت کلاینت امن نگه دارید

در برنامه‌های موبایل، از ذخیره‌سازی امن مثل Keychain (iOS) یا Keystore (Android) استفاده کنید. در برنامه‌های وب، از ذخیره‌سازی در مموری (حافظه موقت) به جای localStorage استفاده کنید تا در برابر حملات XSS آسیب‌پذیر نباشد.

۳. مدیریت انقضای توکن

TokenAuthentication پیش‌فرض DRF تاریخ انقضا ندارد. برای پروژه‌های واقعی، توصیه می‌شود از کتابخانه djangorestframework-simplejwt استفاده کنید که توکن‌های کوتاه‌مدت با قابلیت رفرش ارائه می‌دهد.

عیب‌یابی خطاهای رایج

خطای 401 Unauthorized:

هدر Authorization را درست نوشته‌اید؟ (Token با فاصله از توکن)

توکن منقضی شده یا اشتباه است؟

خطای "Authentication credentials were not provided":

فراموش کرده‌اید authentication_classes را در ویو تنظیم کنید

rest_framework.authtoken را به INSTALLED_APPS اضافه نکرده‌اید

توکن برای کاربر ساخته نمی‌شود:

سیگنال post_save را در جای درستی (فایل models.py) قرار داده‌اید؟

بعد از اضافه کردن authtoken، migrate را اجرا کرده‌اید؟

پیاده‌سازی IsAuthenticated, IsAdminUser, AllowAny

مجوزها (Permissions) چیست؟

بعد از اینکه کاربر احراز هویت شد و شناختیم کیست، باید مشخص کنیم چه کاری مجاز است انجام دهد. به این قوانین می‌گویند مجوز یا Permission.

DRF چند مجوز آماده دارد. در این بخش سه مورد از پرکاربردترین‌ها را یاد می‌گیرید.

AllowAny – دسترسی آزاد

ساده‌ترین مجوز. هیچ محدودیتی اعمال نمی‌کند. هر کسی می‌تواند به API دسترسی داشته باشد. حتی کاربرانی که لاگین نکرده‌اند.

چه موقع استفاده کنیم؟

  • APIهای عمومی مثل صفحه اصلی سایت
  • مستندات API
  • اندپوینت لاگین و ثبت‌نام

کد این بخش:

from rest_framework.permissions import AllowAny
from rest_framework.views import APIView

class PublicView(APIView):
    permission_classes = [AllowAny]
    
    def get(self, request):
        return Response({'message': 'همه می‌توانند این را ببینند'})

نکته مهم: AllowAny مجوز پیش‌فرض DRF است. اگر چیزی تنظیم نکنید، API شما با AllowAny کار می‌کند.

IsAuthenticated – فقط کاربران لاگین شده

فقط کاربرانی که احراز هویت شده‌اند (لاگین کرده‌اند) می‌توانند به API دسترسی داشته باشند. کاربران مهمان (Anonymous) دسترسی ندارند.

چه موقع استفاده کنیم؟

  • پنل کاربری
  • مشاهده پروفایل شخصی
  • ساختن مقاله جدید (فقط کاربران عضو)

کد در APIView:

from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class UserProfileView(APIView):
    permission_classes = [IsAuthenticated]
    
    def get(self, request):
        # request.user لاگین کرده است
        return Response({'username': request.user.username})

کد در ViewSet:

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated

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

IsAdminUser – فقط ادمین‌ها

فقط کاربرانی که is_staff=True دارند (ادمین‌های پنل جنگو) می‌توانند دسترسی داشته باشند.

چه موقع استفاده کنیم؟

  • پنل مدیریت
  • آمار و گزارش‌های حساس
  • عملیات خطرناک مثل حذف انبوه

کد این بخش:

from rest_framework.permissions import IsAdminUser

class AdminStatsView(APIView):
    permission_classes = [IsAdminUser]
    
    def get(self, request):
        return Response({'users_count': User.objects.count()})

ترکیب چند مجوز با AND و OR

AND (هر دو شرط باید برقرار باشد):

با گذاشتن چند کلاس در لیست، همه باید تأیید کنند.

from rest_framework.permissions import IsAuthenticated, IsAdminUser

# کاربر باید هم لاگین کرده باشد و هم ادمین باشد
permission_classes = [IsAuthenticated, IsAdminUser]

OR (حداقل یکی از شرایط):

برای OR باید از Or استفاده کنید.

from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.permissions import BasePermission, SAFE_METHODS

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

مثال عملی: API مقالات با سطوح دسترسی مختلف

فرض کنید می‌خواهید:

  • همه بتوانند مقالات را بخوانند (AllowAny)
  • فقط کاربران لاگین شده بتوانند مقاله بسازند (IsAuthenticated)
  • فقط نویسنده مقاله بتواند آن را ویرایش یا حذف کند (در زیرعنوان بعدی یاد می‌گیرید)

راه حل اول: در سطح ویو (ساده اما محدود)

class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    
    def get_permissions(self):
        if self.action == 'list' or self.action == 'retrieve':
            return [AllowAny()]  # خواندن عمومی
        elif self.action == 'create':
            return [IsAuthenticated()]  # ساخت نیاز به لاگین دارد
        return [IsAdminUser()]  # ویرایش و حذف فقط ادمین

راه حل دوم: با مجوز سفارشی (بهتر)

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAuthorOrReadOnly(BasePermission):
    def has_object_permission(self, request, view, obj):
        # خواندن مجاز است
        if request.method in SAFE_METHODS:
            return True
        # نوشتن فقط برای نویسنده
        return obj.author == request.user

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

تنظیم مجوز پیش‌فرض برای کل پروژه

می‌توانید در settings.py تعیین کنید که به طور پیش‌فرض همه APIها نیاز به احراز هویت داشته باشند:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

سپس در ویوهایی که می‌خواهید عمومی باشند، مجوز را به AllowAny تغییر دهید.

جدول جمع‌بندی مجوزهای آماده

مجوز چه کسی دسترسی دارد کاربرد
AllowAny همه (حتی بدون لاگین) APIهای عمومی، صفحه اصلی
IsAuthenticated فقط کاربران لاگین شده پنل کاربری، ساختن محتوا
IsAdminUser فقط کاربران ادمین (is_staff=True) پنل مدیریت، آمار حساس
IsAuthenticatedOrReadOnly خواندن: همه
نوشتن: کاربران لاگین شده
APIهای با قابلیت خواندن عمومی

نکات مهم در استفاده از مجوزها

نکته اول: مجوزها بعد از احراز هویت اجرا می‌شوند

اول DRF هویت کاربر را مشخص می‌کند (request.user را پر می‌کند). سپس مجوزها بررسی می‌شوند.

نکته دوم: مجوزهای سطح ویو و سطح شیء

مجوزهایی مثل IsAuthenticated در سطح ویو اجرا می‌شوند. اما برای کنترل دسترسی روی هر شیء (مثلاً فقط نویسنده مقاله آن را ویرایش کند) به مجوز سطح شیء نیاز دارید. در زیرعنوان بعدی با DjangoObjectPermissions آشنا می‌شوید.

نکته سوم: اگر مجوزها کار نمی‌کنند

  • آیا authentication_classes را تنظیم کرده‌اید؟ بدون احراز هویت، مجوزها کاربر لاگین شده ندارند.
  • آیا کاربر واقعاً لاگین کرده؟ توکن معتبر دارد؟

تمرین این بخش

یک API برای سایت خبری بسازید با این قوانین:

  • صفحه اصلی مقالات: همه می‌توانند بخوانند (AllowAny)
  • ساخت مقاله جدید: فقط کاربران لاگین شده (IsAuthenticated)
  • ویرایش و حذف مقاله: فقط ادمین (IsAdminUser)
  • از get_permissions برای پیاده‌سازی استفاده کنید.

دسترسی سطحی: DjangoObjectPermissions

مشکل مجوزهای سطح ویو

مجوزهایی مثل IsAuthenticated و IsAdminUser در سطح ویو کار می‌کنند. یعنی اگر کاربر شرط را داشت، به کل ویو دسترسی پیدا می‌کند.

اما در دنیای واقعی، دسترسی‌ها معمولاً ظریف‌تر هستند.

یک مثال بزنم.

فرض کنید سیستمی دارید که کاربران می‌توانند مقاله بنویسند. انتظار دارید:

  • هر کاربر فقط مقالات خودش را ویرایش کند
  • کسی نتواند مقاله دیگران را حذف کند
  • ادمین همه مقالات را مدیریت کند

مجوزهای سطح ویو نمی‌توانند این تشخیص را انجام بدهند. چون نمی‌دانند کاربر با کدام شیء خاص سروکار دارد.

اینجا DjangoObjectPermissions وارد می‌شود.

DjangoObjectPermissions چیست؟

این مجوز از سیستم مجوزهای خود جنگو استفاده می‌کند. همان مجوزهایی که در پنل ادمین برای مدل‌ها تعریف می‌کنید.

سه مجوز اصلی برای هر مدل وجود دارد:

  • view – مشاهده شیء
  • add – ساختن شیء جدید
  • change – ویرایش شیء
  • delete – حذف شیء

DjangoObjectPermissions چک می‌کند کاربر روی آن شیء خاص این مجوز را دارد یا نه.

قدم اول: فعال کردن مجوزهای مدل

وقتی یک مدل می‌سازید، جنگو خودکار این مجوزها را ایجاد می‌کند. می‌توانید در پنل ادمین به کاربران و گروه‌ها مجوز بدهید.

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    
    class Meta:
        permissions = [
            ("can_publish", "Can publish article"),  # مجوز سفارشی
        ]

بعد از اجرای makemigrations و migrate، این مجوزها در دیتابیس ساخته می‌شوند.

قدم دوم: تنظیم مجوز در ویو

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import DjangoObjectPermissions
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [DjangoObjectPermissions]

با این تنظیم، DRF خودکار چک می‌کند:

  • کاربر مجوز view روی این مقاله را دارد؟ (برای درخواست GET)
  • کاربر مجوز change روی این مقاله را دارد؟ (برای PUT و PATCH)
  • کاربر مجوز delete روی این مقاله را دارد؟ (برای DELETE)
  • کاربر مجوز add روی این مدل را دارد؟ (برای POST)

قدم سوم: دادن مجوز به کاربران

روش اول: از طریق پنل ادمین

وارد پنل ادمین شوید. به بخش "Users" بروید. کاربر مورد نظر را انتخاب کنید. در بخش "User permissions"، مجوزهای article | article | Can view article، Can change article، و Can delete article را اضافه کنید.

روش دوم: با کد

from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
from myapp.models import Article

user = User.objects.get(username='ali')
content_type = ContentType.objects.get_for_model(Article)
permission = Permission.objects.get(
    codename='change_article',
    content_type=content_type,
)
user.user_permissions.add(permission)

قدم چهارم: اختصاص دادن نویسنده به شیء

DjangoObjectPermissions به تنهایی نمی‌داند نویسنده مقاله چه کسی است. باید در ویو مشخص کنید که هنگام ساخت مقاله، author را به کاربر فعلی اختصاص دهید.

class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [DjangoObjectPermissions]
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

حالا وقتی کاربر جدید مقاله می‌سازد، خودکار نویسنده آن مشخص می‌شود.

ترکیب با مجوزهای دیگر

معمولاً DjangoObjectPermissions را با IsAuthenticated ترکیب می‌کنید:

from rest_framework.permissions import IsAuthenticated, DjangoObjectPermissions

class ArticleViewSet(ModelViewSet):
    permission_classes = [IsAuthenticated, DjangoObjectPermissions]

یعنی اول چک می‌کند کاربر لاگین کرده، بعد چک می‌کند مجوز روی آن شیء خاص را دارد.

محدودیت مهم

DjangoObjectPermissions درخواست POST (ساختن شیء جدید) را چک می‌کند. اما در لحظه POST، هنوز شیء ساخته نشده. پس مجوز سطح شیء معنی ندارد. در این حالت، چک می‌کند کاربر مجوز add روی کل مدل را دارد.

برای درخواست‌های GET (لیست)، مجوز روی تک تک اشیاء چک می‌شود. کاربر فقط آن اشیایی را می‌بیند که مجوز view روی آن‌ها دارد.

مثال کامل: هر کاربر فقط مقالات خودش را ببیند و ویرایش کند

from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, DjangoObjectPermissions
from .models import Article
from .serializers import ArticleSerializer

class ArticleViewSet(ModelViewSet):
    serializer_class = ArticleSerializer
    permission_classes = [IsAuthenticated, DjangoObjectPermissions]
    
    def get_queryset(self):
        user = self.request.user
        if user.is_superuser:
            return Article.objects.all()
        return Article.objects.filter(author=user)
    
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

کاربر ali فقط مقالات خودش را می‌بیند. و فقط می‌تواند مقالات خودش را ویرایش یا حذف کند.

جدول مقایسه مجوزها

مجوز سطح چه چیزی را چک میکند
IsAuthenticated ویو کاربر لاگین کرده؟
IsAdminUser ویو کاربر ادمین است؟
DjangoObjectPermissions شی کاربر مجوز view، change، یا delete روی این شیء خاص را دارد؟

عیب‌یابی خطاهای رایج

خطا: کاربر نمی‌تواند مقاله خودش را ویرایش کند

بررسی کنید مجوز change_article را به کاربر داده‌اید؟ فقط داشتن نویسندگی کافی نیست. باید مجوز را هم بدهید.

خطا: کاربر مقالات دیگران را می‌بیند

در get_queryset فیلتر نزده‌اید. DjangoObjectPermissions به تنهایی لیست را فیلتر نمی‌کند. باید خودتان در get_queryset محدودیت اعمال کنید.

خطا: مجوزها در پنل ادمین دیده نمی‌شوند

حتماً makemigrations و migrate را اجرا کرده‌اید؟ مجوزهای مدل بعد از این دستورات ساخته می‌شوند.

چگونه Permission سفارشی بسازیم

چه موقع به مجوز سفارشی نیاز داریم؟

مجوزهای آماده DRF (IsAuthenticated، IsAdminUser، DjangoObjectPermissions) خیلی از نیازهای رایج را پوشش می‌دهند.

اما همیشه اینطور نیست.

چند مثال بزنم:

  • فقط کاربرانی که حداقل ۵ مقاله نوشته باشند، اجازه ساخت مقاله جدید دارند
  • فقط در ساعات کاری (۹ صبح تا ۵ بعدازظهر) امکان ویرایش وجود داشته باشد
  • کاربر فقط بتواند روی مقالاتی که خودش ساخته، عملیات «انتشار» را انجام دهد

این قوانین خاص را نمی‌توانید با مجوزهای آماده پیاده کنید. باید خودتان بنویسید.

ساختار یک مجوز سفارشی

هر مجوز سفارشی یک کلاس است که از BasePermission ارث‌بری می‌کند. دو متد اصلی دارد:

has_permission – سطح ویو. قبل از رسیدن درخواست به متد ویو اجرا می‌شود.

has_object_permission – سطح شیء. بعد از پیدا شدن شیء اجرا می‌شود.

from rest_framework.permissions import BasePermission

class MyCustomPermission(BasePermission):
    def has_permission(self, request, view):
        # True یعنی اجازه دارد، False یعنی دسترسی ندارد
        return True
    
    def has_object_permission(self, request, view, obj):
        return True

مثال اول: محدودیت بر اساس تعداد مقالات

فرض کنید می‌خواهید کاربر فقط بعد از نوشتن ۵ مقاله، اجازه ساخت مقاله جدید داشته باشد.

from rest_framework.permissions import BasePermission
from .models import Article

class CanCreateAfterFiveArticles(BasePermission):
    def has_permission(self, request, view):
        # این مجوز فقط برای عملیات CREATE معنی دارد
        if view.action != 'create':
            return True
        
        user = request.user
        if not user.is_authenticated:
            return False
        
        article_count = Article.objects.filter(author=user).count()
        return article_count >= 5

حالا می‌توانید در ویوی خود از آن استفاده کنید:

class ArticleViewSet(ModelViewSet):
    permission_classes = [IsAuthenticated, CanCreateAfterFiveArticles]

مثال دوم: محدودیت زمانی (ساعات کاری)

فرض کنید می‌خواهید API فقط بین ساعت ۹ صبح تا ۵ بعدازظهر در دسترس باشد.

from rest_framework.permissions import BasePermission
from datetime import datetime

class BusinessHoursOnly(BasePermission):
    def has_permission(self, request, view):
        now = datetime.now()
        current_hour = now.hour
        # فقط بین ۹ تا ۱۷ (ساعت ۵ عصر)
        return 9 <= current_hour < 17

مثال سوم: دسترسی بر اساس وضعیت کاربر

فرض کنید کاربران دو نوع هستند: معمولی و ویژه (VIP). فقط کاربران ویژه می‌توانند محتوای خاصی را ببینند.

class VIPOnly(BasePermission):
    def has_permission(self, request, view):
        user = request.user
        if not user.is_authenticated:
            return False
        return hasattr(user, 'profile') and user.profile.is_vip

مثال چهارم: ترکیب مجوز سطح ویو و سطح شیء

این مثال ترکیبی نشان می‌دهد چطور هم سطح ویو و هم سطح شیء را کنترل کنید.

from rest_framework.permissions import BasePermission, SAFE_METHODS

class IsAuthorOrAdminOrReadOnly(BasePermission):
    """
    - همه می‌توانند بخوانند (GET)
    - فقط نویسنده یا ادمین می‌توانند ویرایش یا حذف کنند
    """
    
    def has_permission(self, request, view):
        # برای درخواست‌های SAFE (GET, HEAD, OPTIONS) اجازه بده
        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 or request.user.is_staff

مثال پنجم: محدودیت بر اساس محتوای شیء

فرض کنید مقاله‌های تایید نشده را فقط نویسنده و ادمین می‌توانند ببینند.

class UnpublishedAccess(BasePermission):
    def has_object_permission(self, request, view, obj):
        # اگر مقاله منتشر شده، همه می‌توانند ببینند
        if obj.is_published:
            return True
        
        # اگر منتشر نشده، فقط نویسنده یا ادمین
        return obj.author == request.user or request.user.is_staff

استفاده از مجوز سفارشی در ویو

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

نکته: ترتیب مجوزها مهم نیست. همه باید تأیید کنند تا دسترسی داده شود.

استفاده از مجوز سفارشی فقط برای یک اکشن خاص

گاهی فقط یک اکشن خاص به مجوز متفاوت نیاز دارد.

class ArticleViewSet(ModelViewSet):
    permission_classes = [IsAuthenticated]
    
    def get_permissions(self):
        if self.action == 'publish':
            return [IsAdminUser()]
        if self.action == 'create':
            return [CanCreateAfterFiveArticles()]
        return super().get_permissions()
    
    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        article = self.get_object()
        article.is_published = True
        article.save()
        return Response({'status': 'published'})

نکات مهم در نوشتن مجوز سفارشی

نکته اول: به SAFE_METHODS توجه کنید

SAFE_METHODS شامل GET، HEAD، و OPTIONS است. معمولاً این متدها را برای همه آزاد می‌گذارید.

from rest_framework.permissions import SAFE_METHODS

نکته دوم: has_permission قبل از has_object_permission اجرا می‌شود

اگر has_permission برگرداند False، اصلاً نوبت به has_object_permission نمی‌رسد.

نکته سوم: در لیست (list)، has_object_permission اجرا نمی‌شود

برای درخواست GET لیست، باید دسترسی را در has_permission کنترل کنید. یا در get_queryset فیلتر اعمال کنید.

نکته چهارم: خطای 403 Forbidden

وقتی مجوز دسترسی نمی‌دهد، DRF خودکار خطای 403 را برمی‌گرداند. نیازی نیست خودتان خطا بدهید.

تمرین این بخش:

یک مجوز سفارشی بنویسید به اسم CanEditAfterOneHour. قانون: کاربر فقط یک ساعت بعد از ساخت مقاله می‌تواند آن را ویرایش کند. (نکته: زمان ساخت مقاله را در مدل ذخیره کنید)

جمع‌بندی درس احراز هویت و مجوزها

در این درس، با یکی از مهم‌ترین بخش‌های DRF برای بازار کار آشنا شدید.

سه روش اصلی احراز هویت را یاد گرفتید. BasicAuthentication ساده اما کم‌امنیت. SessionAuthentication که با پنل ادمین جنگو هماهنگ است. و TokenAuthentication که استاندارد اصلی APIهای مدرن محسوب می‌شود.

TokenAuthentication را قدم به قدم پیاده‌سازی کردید. از تنظیمات اولیه تا ساخت اندپوینت لاگین و دریافت توکن. یاد گرفتید چطور توکن را در هدر درخواست بفرستید و در ویوها از آن استفاده کنید.

بعد از احراز هویت، نوبت به مجوزها رسید. با سه مجوز آماده AllowAny، IsAuthenticated، و IsAdminUser آشنا شدید. دیدید هر کدام چه موقع به کار می‌روند و چطور ترکیب می‌شوند.

DjangoObjectPermissions را یاد گرفتید. مجوزی که دسترسی را روی هر شیء به صورت جداگانه کنترل می‌کند. دیدید چطور با کمک مجوزهای خود جنگو، می‌توانید تعیین کنید هر کاربر روی کدام مقاله خاص اجازه ویرایش یا حذف دارد.

در انتها، مجوزهای سفارشی را ساختید. برای مواقعی که قوانین دسترسی از آن چیزی که مجوزهای آماده پوشش می‌دهند، پیچیده‌تر است.

از این درس چه چیزی باید به خاطر بسپارید؟

۱. هیچ API واقعی بدون احراز هویت و مجوز کامل نیست. حتی اگر امروز لازم ندارید، بعداً اضافه کردن آن سخت می‌شود.

۲. TokenAuthentication را به عنوان انتخاب اصلی برای پروژه‌های جدید در نظر بگیرید. مخصوصاً اگر فرانت‌اند شما React، Vue، یا اپلیکیشن موبایل است.

۳. مجوزها را همیشه جدی بگیرید. یک مجوز فراموش شده می‌تواند داده‌های حساس را در معرض همه قرار دهد.

۴. اگر نیاز به قانون خاصی دارید که در مجوزهای آماده نیست، مجوز سفارشی بنویسید. سخت نیست و خیلی به کارتان می‌آید.

درس بعدی چیست؟

حالا که APIهای شما امن شده‌اند، وقت آن رسیده که کاربردی‌ترشان کنید.

در درس بعدی، با فیلتر، جستجو، و مرتب‌سازی آشنا می‌شوید. یاد می‌گیرید چطور به کاربر اجازه دهید فقط مقالات یک نویسنده خاص را ببیند. یا بین عناوین جستجو کند. یا نتایج را بر اساس تاریخ مرتب کند.

این قابلیت‌ها در هر API واقعی که داده‌های زیادی دارد، ضروری هستند.

برای درس بعد، همان پروژه و مدل Article فعلی را نگه دارید. فقط چند مقاله نمونه به آن اضافه کنید تا بتوانید فیلتر و جستجو را تست کنید.