<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"><channel><title>نوشته های Mohammad saleh</title><link>https://virgool.ir/feed/@mohammad3a1eh</link><description>مثلا برنامه نویس</description><language>fa</language><pubDate>2026-11-11 14:49:32</pubDate><image><url>https://files.virgool.ir/upload/users/2732113/avatar/BYHXqU.jpg</url><title>Mohammad saleh</title><link>https://virgool.ir/@mohammad3a1eh</link></image><item><title>ساخت سیستم احراز هویت با Simple JWT در Django: گام‌به‌گام تا امنیت کامل</title><link>https://virgool.ir/@mohammad3a1eh/ساخت-سیستم-احراز-هویت-با-simple-jwt-در-django-گام-به-گام-تا-امنیت-کامل-gg3zu6ubrjc9</link><description>در پروژه‌های مبتنی بر API، یکی از چالش‌های اصلی، پیاده‌سازی احراز هویت امن و قابل اطمینان برای کاربران است؛ مخصوصاً وقتی پای نگهداری توکن‌ها به میان می‌آید.در این مطلب، می‌خواهیم با استفاده از Django REST Framework و کتابخانه‌ی Simple JWT، سیستمی برای مدیریت احراز هویت طراحی کنیم که هم امن باشد، هم از نظر تجربه کاربری روان و بدون دردسر.در مسیر این آموزش با مفاهیمی مثل:تولید و مدیریت توکن‌های JWTذخیره توکن‌ها در کوکیبلک‌لیست کردن رفرش توکن‌هاآشنا خواهیم شد. هدف ما ساختن سیستمی‌ست که بتواند هم نیازهای امنیتی را پوشش دهد و هم در پروژه‌های واقعی و چندفرانتی کاربردی باشد.مرحله اول: ساخت محیط مجازی و فعال سازیوارد پوشه‌ای شوید که می‌خواهید پروژه‌ی جنگو (Django) در آن ساخته شود، سپس دستورات زیر را برای ایجاد محیط مجازی و فعال‌سازی آن اجرا کنید:python -m venv .venv&#xA;.\.venv\Scripts\activate.ps1 # powershellمرحله دوم: نصب کتابخانه‌های پایتونpip install django&#xA;pip install djangorestframework &#xA;pip install djangorestframework-simplejwt&#xA;pip install djangorestframework-simplejwt[token_blacklist]مرحله سوم: ایجاد پروژه جنگو و اپ مدیریت کاربرانdjango-admin startproject config .این دستور باعث می‌شه فایل‌های پروژه در همین مسیر ساخته بشن، نه داخل پوشه‌ی جدید.python manage.py startapp accountsمرحله چهارم: انجام تنظیمات اولیه پروژه جنگو و اعمال تغییرات به پایگاه دادهدر ادامه، باید اپلیکیشن‌هایی که در پروژه استفاده می‌کنیم، از جمله ماژول‌های مربوط به REST و JWT را در تنظیمات Django فعال کنیم.# config\settings.py&#xA;INSTALLED_APPS = [&#xA;    &#39;rest_framework&#39;,&#xA;    &#39;rest_framework_simplejwt.token_blacklist&#39;,&#xA;    &#39;accounts&#39;,&#xA;]در این مرحله، مشخص می‌کنیم که احراز هویت پیش‌فرض پروژه از نوع JWT باشد تا Django REST Framework توکن‌های JWT را به‌عنوان روش تأیید هویت کاربران بشناسد.# config\settings.py&#xA;REST_FRAMEWORK = {&#xA;    &#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: (&#xA;        &#39;rest_framework_simplejwt.authentication.JWTAuthentication&#39;,&#xA;    ),&#xA;}در ادامه، با اضافه کردن تنظیمات مربوط به Simple JWT، مشخص می‌کنیم که توکن‌ها چه مدت اعتبار داشته باشند، در زمان رفرش شدن آیا توکن قبلی بلاک شود یا نه، و هدر توکن‌ها با چه پیشوندی ارسال شود.# config\settings.py&#xA;from datetime import timedelta&#xA;&#xA;SIMPLE_JWT = {&#xA;    &#39;ACCESS_TOKEN_LIFETIME&#39;: timedelta(minutes=5),&#xA;    &#39;REFRESH_TOKEN_LIFETIME&#39;: timedelta(days=7),&#xA;    &#39;ROTATE_REFRESH_TOKENS&#39;: True,&#xA;    &#39;BLACKLIST_AFTER_ROTATION&#39;: True,&#xA;    &#39;AUTH_HEADER_TYPES&#39;: (&#39;Bearer&#39;,),&#xA;    &#39;AUTH_TOKEN_CLASSES&#39;: (&#39;rest_framework_simplejwt.tokens.AccessToken&#39;,),&#xA;    &#39;TOKEN_BLACKLIST_ENABLED&#39;: True,&#xA;}ابتدا باید تغییرات ایجاد شده در مدل‌ها را با دستور makemigrations به فایل‌های مایگریشن تبدیل کنیم، سپس با اجرای دستور migrate این تغییرات را به پایگاه داده اعمال کنیم.python manage.py makemigrations&#xA;python manage.py migrateمرحله پنجم: پیاده‌سازی رجیستر، لاگین، و رفرش توکن با Simple JWT و ذخیره توکن‌ها در کوکی HttpOnlyدر این بخش، یک Serializer برای ثبت‌نام کاربران با استفاده از مدل پیش‌فرض User جنگو می‌سازیم. این Serializer دو فیلد رمز عبور (password) و تکرار آن (password2) را دریافت می‌کند تا صحت مطابقت رمز عبور را بررسی کند. سپس، با متد create کاربر جدید را ساخته و پسورد را به صورت امن (هش شده) ذخیره می‌کند.# accounts\serializers.py&#xA;from django.contrib.auth.password_validation import validate_password&#xA;from django.core.exceptions import ValidationError as DjangoValidationError&#xA;from django.contrib.auth.models import User&#xA;from rest_framework_simplejwt.serializers import TokenRefreshSerializer&#xA;from rest_framework import serializers&#xA;&#xA;class RegisterSerializer(serializers.ModelSerializer):&#xA;    password = serializers.CharField(write_only=True, required=True, style={&#39;input_type&#39;: &#39;password&#39;})&#xA;    password2 = serializers.CharField(write_only=True, required=True, style={&#39;input_type&#39;: &#39;password&#39;})&#xA;&#xA;    class Meta:&#xA;        model = User&#xA;        fields = (&#39;username&#39;, &#39;email&#39;, &#39;password&#39;, &#39;password2&#39;)&#xA;&#xA;    def validate(self, data):&#xA;        if data[&#39;password&#39;] != data[&#39;password2&#39;]:&#xA;            raise serializers.ValidationError({&#34;password2&#34;: &#34;Passwords must match.&#34;})&#xA;        try:&#xA;            validate_password(data[&#39;password&#39;])&#xA;        except DjangoValidationError as e:&#xA;            raise serializers.ValidationError({&#39;password&#39;: list(e.messages)})&#xA;        return data&#xA;&#xA;    def create(self, validated_data):&#xA;        user = User.objects.create(&#xA;            username=validated_data[&#39;username&#39;],&#xA;            email=validated_data.get(&#39;email&#39;, &#39;&#39;)&#xA;        )&#xA;        user.set_password(validated_data[&#39;password&#39;])&#xA;        user.save()&#xA;        return user&#xA;&#xA;class CookieTokenRefreshSerializer(TokenRefreshSerializer):&#xA;    pass&#xA;با این روش، ما امنیت رمز عبور را تضمین می‌کنیم و همچنین از ورود رمزهای عبور ناسازگار جلوگیری می‌کنیم. همچنین چون از مدل پیش‌فرض User استفاده شده، نیازی به تعریف مدل اختصاصی نیست و می‌توانیم به سرعت فرآیند ثبت‌نام را پیاده کنیم.در این مرحله، یک view برای ثبت‌نام کاربران می‌سازیم که داده‌های ورودی رو از طریق RegisterSerializer پردازش می‌کنه و در صورت اعتبارسنجی موفق، یک کاربر جدید در دیتابیس ایجاد می‌شه. چون این endpoint برای کاربران مهمان (غیر لاگین‌شده) قابل دسترسه، سطح دسترسی اون رو AllowAny قرار می‌دیم.# accounts\views.py&#xA;from rest_framework import generics, permissions, status&#xA;from rest_framework.response import Response&#xA;from .serializers import RegisterSerializer&#xA;from rest_framework_simplejwt.tokens import RefreshToken&#xA;&#xA;ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60&#xA;REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60&#xA;&#xA;class RegisterView(generics.CreateAPIView):&#xA;    serializer_class = RegisterSerializer&#xA;    permission_classes = [permissions.AllowAny]&#xA;&#xA;    def post(self, request, *args, **kwargs):&#xA;        # Register user&#xA;        serializer = self.get_serializer(data=request.data)&#xA;        serializer.is_valid(raise_exception=True)&#xA;        user = serializer.save()&#xA;&#xA;        # Generate tokens&#xA;        refresh = RefreshToken.for_user(user)&#xA;        access = refresh.access_token&#xA;&#xA;        # Response object&#xA;        res = Response({&#39;detail&#39;: &#39;User registered and logged in.&#39;}, status=status.HTTP_201_CREATED)&#xA;&#xA;        # Set tokens in HttpOnly cookies&#xA;        res.set_cookie(&#xA;            key=&#39;access_token&#39;,&#xA;            value=str(access),&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=ACCESS_TOKEN_LIFETIME_SECONDS&#xA;        )&#xA;&#xA;        res.set_cookie(&#xA;            key=&#39;refresh_token&#39;,&#xA;            value=str(refresh),&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=REFRESH_TOKEN_LIFETIME_SECONDS&#xA;        )&#xA;&#xA;        return resدر این view، بعد از اینکه کاربر جدید با موفقیت ثبت‌نام می‌کنه، بلافاصله یک جفت توکن JWT (شامل Access و Refresh Token) برای او ایجاد می‌شه.به‌جای اینکه توکن‌ها رو در بدنه‌ی پاسخ برگردونیم (که امنیت کمتری داره)، اون‌ها رو با استفاده از set_cookie در کوکی‌های HttpOnly قرار دادیم. این یعنی:توکن‌ها از طریق JavaScript قابل دسترسی نیستند (جلوگیری از حملات XSS).با تنظیم گزینه‌ی secure=True، این کوکی‌ها فقط در کانکشن‌های HTTPS ارسال می‌شن.گزینه‌ی samesite=&#39;Lax&#39; یا &#39;Strict&#39; کمک می‌کنه از حملات CSRF جلوگیری بشه.همچنین زمان انقضای کوکی‌ها با max_age مشخص شده که باید با تنظیمات SIMPLE_JWT هماهنگ باشه.با این کار، کاربر پس از ثبت‌نام به‌صورت خودکار لاگین هم می‌شه و توکن‌ها به‌صورت امن در مرورگر ذخیره می‌شن، بدون نیاز به دخالت مستقیم فرانت‌اند برای ذخیره‌سازی دستی توکن.اما در حالت عادی، کتابخانه‌ی Simple JWT انتظار داره که توکن JWT در هدر Authorization هر درخواست ارسال بشه.ولی وقتی ما توکن JWT رو به‌صورت امن (با HttpOnly) در کوکی ذخیره می‌کنیم، فرانت‌اند نمی‌تونه مستقیماً این توکن رو بخونه و داخل هدر بذاره.برای حل این مشکل، یک middleware سفارشی می‌نویسیم که قبل از رسیدن request به viewها، کوکی حاوی توکن را بررسی کند و در صورت وجود، توکن را در هدر Authorization قرار دهد. اینطوری Simple JWT بدون نیاز به هیچ تغییر خاصی توکن را شناسایی می‌کند.# config\middleware\jwt_cookie_auth.py&#xA;class JWTTokenCookieMiddleware:&#xA;    def __init__(self, get_response):&#xA;        self.get_response = get_response&#xA;&#xA;    def __call__(self, request):&#xA;        access_token = request.COOKIES.get(&#39;access_token&#39;)&#xA;&#xA;        if access_token:&#xA;            request.META[&#39;HTTP_AUTHORIZATION&#39;] = f&#39;Bearer {access_token}&#39;&#xA;&#xA;        return self.get_response(request)حالا باید این میدل‌ور رو قبل از میدل‌ور AuthenticationMiddleware اضافه کنیم.# config\settings.py&#xA;MIDDLEWARE = [&#xA;    &#39;config.middleware.jwt_cookie_auth.JWTTokenCookieMiddleware&#39;,&#xA;    &#39;django.middleware.security.SecurityMiddleware&#39;,&#xA;    &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;,&#xA;]خب الان که بعد از ساخت حساب کاربری توکن‌ها رو توی کوکی تنظیم کردیم باید همین کار رو برای ویوهای رفرش توکن و لاگین انجام بدیم تا همه چیز درست کار کنه. اول رفرش توکن:# accounts\views.py&#xA;from rest_framework_simplejwt.views import TokenRefreshView&#xA;from rest_framework_simplejwt.exceptions import InvalidToken, TokenError&#xA;from rest_framework.response import Response&#xA;from rest_framework import status&#xA;from datetime import timedelta&#xA;&#xA;ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60&#xA;REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60&#xA;&#xA;class CustomTokenRefreshView(APIView):&#xA;    def post(self, request, *args, **kwargs):&#xA;        refresh_token = request.COOKIES.get(&#39;refresh_token&#39;)&#xA;        if not refresh_token:&#xA;            return Response({&#34;refresh&#34;: [&#34;Refresh token not found in cookies.&#34;]}, status=400)&#xA;&#xA;        serializer = CookieTokenRefreshSerializer(data={&#39;refresh&#39;: refresh_token}, context={&#39;request&#39;: request})&#xA;&#xA;        try:&#xA;            serializer.is_valid(raise_exception=True)&#xA;        except TokenError as e:&#xA;            raise InvalidToken(e.args[0])&#xA;&#xA;        access_token = serializer.validated_data.get(&#34;access&#34;)&#xA;        refresh_token = serializer.validated_data.get(&#34;refresh&#34;)&#xA;&#xA;        response = Response({&#34;detail&#34;: &#34;Tokens refreshed and stored in cookies.&#34;})&#xA;&#xA;        response.set_cookie(&#xA;            key=&#39;access_token&#39;,&#xA;            value=access_token,&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=ACCESS_TOKEN_LIFETIME_SECONDS&#xA;        )&#xA;&#xA;        response.set_cookie(&#xA;            key=&#39;refresh_token&#39;,&#xA;            value=refresh_token,&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=REFRESH_TOKEN_LIFETIME_SECONDS&#xA;        )&#xA;&#xA;        return responseو بعد لاگین:# accounts\views.py&#xA;from rest_framework_simplejwt.views import TokenObtainPairView&#xA;from rest_framework_simplejwt.exceptions import InvalidToken, TokenError&#xA;from rest_framework.response import Response&#xA;from rest_framework import status&#xA;&#xA;ACCESS_TOKEN_LIFETIME_SECONDS = 5 * 60&#xA;REFRESH_TOKEN_LIFETIME_SECONDS = 7 * 24 * 60 * 60&#xA;&#xA;class CustomTokenObtainPairView(TokenObtainPairView):&#xA;    def post(self, request, *args, **kwargs):&#xA;        serializer = self.get_serializer(data=request.data)&#xA;&#xA;        try:&#xA;            serializer.is_valid(raise_exception=True)&#xA;        except TokenError as e:&#xA;            raise InvalidToken(e.args[0])&#xA;&#xA;        access_token = serializer.validated_data.get(&#39;access&#39;)&#xA;        refresh_token = serializer.validated_data.get(&#39;refresh&#39;)&#xA;&#xA;        response = Response({&#34;detail&#34;: &#34;Login successful. Tokens stored in cookies.&#34;}, status=status.HTTP_200_OK)&#xA;&#xA;        response.set_cookie(&#xA;            key=&#39;access_token&#39;,&#xA;            value=access_token,&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=ACCESS_TOKEN_LIFETIME_SECONDS &#xA;        )&#xA;&#xA;        response.set_cookie(&#xA;            key=&#39;refresh_token&#39;,&#xA;            value=refresh_token,&#xA;            httponly=True,&#xA;            secure=True,&#xA;            samesite=&#39;Lax&#39;,&#xA;            max_age=REFRESH_TOKEN_LIFETIME_SECONDS &#xA;        )&#xA;&#xA;        return responseمرحله ششم: پیاده‌سازی لاگ اوتبرای پیاده‌سازی عملیات لاگ‌اوت در سیستم JWT که توکن‌ها در کوکی‌های HttpOnly ذخیره شده‌اند، لازم است توکن‌های ذخیره شده در کوکی حذف شوند و همچنین توکن رفرش بلاک‌لیست شود تا دیگر امکان استفاده مجدد از آن وجود نداشته باشد. این کار باعث می‌شود امنیت سیستم بالاتر رفته و کاربر عملاً از سیستم خارج شود.ویوی زیر این مراحل را به صورت کامل انجام می‌دهد: ابتدا توکن رفرش را از کوکی دریافت می‌کند، سپس آن را در لیست سیاه قرار می‌دهد و در نهایت کوکی‌های مربوط به توکن‌ها را حذف می‌کند.# accounts\views.py&#xA;from rest_framework.views import APIView&#xA;from rest_framework.response import Response&#xA;from rest_framework import status&#xA;from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken&#xA;from rest_framework.permissions import IsAuthenticated&#xA;&#xA;class LogoutView(APIView):&#xA;    permission_classes = [IsAuthenticated]&#xA;&#xA;    def post(self, request):&#xA;        try:&#xA;            refresh_token = request.COOKIES.get(&#39;refresh_token&#39;)&#xA;            if refresh_token is None:&#xA;                return Response({&#34;detail&#34;: &#34;No refresh token found&#34;}, status=status.HTTP_400_BAD_REQUEST)&#xA;&#xA;            token = OutstandingToken.objects.get(token=refresh_token)&#xA;            BlacklistedToken.objects.create(token=token)&#xA;&#xA;            response = Response({&#34;detail&#34;: &#34;Logged out successfully&#34;}, status=status.HTTP_200_OK)&#xA;            response.delete_cookie(&#39;access_token&#39;)&#xA;            response.delete_cookie(&#39;refresh_token&#39;)&#xA;            return response&#xA;&#xA;        except OutstandingToken.DoesNotExist:&#xA;            return Response({&#34;detail&#34;: &#34;Invalid refresh token&#34;}, status=status.HTTP_400_BAD_REQUEST)با اجرای این ویو، توکن رفرش کاربر به لیست سیاه اضافه می‌شود و کوکی‌های توکن از مرورگر حذف می‌شوند. این باعث می‌شود که دیگر نتوان از توکن‌های قبلی برای دسترسی یا رفرش استفاده کرد و کاربر به طور کامل از سیستم خارج می‌شود.این روش یکی از امن‌ترین راه‌ها برای پیاده‌سازی لاگ‌اوت در سیستم‌های مبتنی بر JWT است که توکن‌ها را در کوکی HttpOnly ذخیره می‌کنند.مرحله هفتم: تعریف و تنظیم مسیرهای API در Django برای احراز هویت JWTبعد از اینکه ویوهای مربوط به رجیستر، لاگین، رفرش توکن و لاگ‌اوت را نوشتیم، باید آن‌ها را در فایل urls.py تعریف کنیم تا بتوانیم از طریق آدرس‌های مشخص شده، این عملیات‌ها را فراخوانی کنیم. معمولاً یک فایل urls.py در اپ مربوط به احراز هویت می‌سازیم و مسیرهای مورد نیاز را آنجا قرار می‌دهیم.# accounts\urls.py&#xA;from django.urls import path&#xA;from accounts.views import RegisterView, CustomTokenObtainPairView, CustomTokenRefreshView, LogoutView&#xA;&#xA;urlpatterns = [&#xA;    path(&#39;register/&#39;, RegisterView.as_view(), name=&#39;register&#39;),&#xA;    path(&#39;login/&#39;, CustomTokenObtainPairView.as_view(), name=&#39;login&#39;),&#xA;    path(&#39;refresh/&#39;, CustomTokenRefreshView.as_view(), name=&#39;token_refresh&#39;),&#xA;    path(&#39;logout/&#39;, LogoutView.as_view(), name=&#39;logout&#39;),&#xA;]# config\urls.py&#xA;from django.contrib import admin&#xA;from django.urls import path, include&#xA;&#xA;urlpatterns = [&#xA;    path(&#39;admin/&#39;, admin.site.urls),&#xA;    path(&#39;api/&#39;, include(&#39;accounts.urls&#39;)), &#xA;]با این تنظیمات، API ما مسیرهای مشخصی برای عملیات‌های اصلی احراز هویت دارد. این آدرس‌ها را فرانت‌اند می‌تواند برای ارسال درخواست‌های HTTP به کار ببرد.حالا مسیرهای API به این شکل خواهند بود:http://127.0.0.1:8000/api/register/&#xA;http://127.0.0.1:8000/api/login/&#xA;http://127.0.0.1:8000/api/refresh/&#xA;http://127.0.0.1:8000/api/logout/مرحله هشتم: تست سیستم و اجرای سیستم لوکالبا دستور runserver پروژه رو اجرا می‌کنیم و پروژه معمولا روی پورت 8000 منتظر دریافت درخواست است. python manage.py runserverاگه ادرس http://127.0.0.1:8000 رو توی مرورگر باز کنیم با همچین صفحه ای روبه‌رو می‌شید.در ادامه نمونه درخواست‌های ارسال شده با نرم‌افزار HTTPie رو قرار می‌بینید.درخواست رجیستردرخواست لاگ اوتدرخواست رفرش توکندرخواست لاگیناگر هنگام اجرا با خطایی مواجه شدید یا بخشی از آموزش برایتان مبهم بود، می‌توانید به کد کامل اون رو دسترسی داشته باشید: github.com/mohammad3a1eh/django-api-simple-jwtتمام فایل‌ها و تنظیمات به‌صورت یکپارچه در آن‌جا قرار داده شده‌اند.در نهایت، همان‌طور که از عنوان مطلب «ساخت سیستم احراز هویت با Simple JWT در Django: گام‌به‌گام تا امنیت کامل» برمی‌آید، هدف این آموزش فراهم کردن یک مسیر شفاف و کاربردی برای پیاده‌سازی این سیستم است. اما طبیعی‌ست که امنیت کامل، نیازمند بررسی دقیق‌تر، تست‌های امنیتی و تنظیمات نهایی متناسب با شرایط محیط اجرایی باشد.موفق باشید.</description><category>Mohammad saleh</category><author>Mohammad saleh</author><pubDate>Wed, 07 Jan 2026 12:55:37 +0330</pubDate></item></channel></rss>