مقدمه
توکن وب JSON (JWT) یک روش فشرده و امضاشده برای انتقال امن اطلاعات بین طرفین بهصورت یک شیء JSON است. در APIهای وب، JWTها گزینهای رایج برای احراز هویت هستند، زیرا سرور میتواند بدون اینکه چیزی را در پایگاه داده جستوجو کند، آنها را اعتبارسنجی کند.
این روش بین توسعهدهندگان محبوب است چون:
-
بدون وضعیت (Stateless): یک JWT معتبر تمام دادههای لازم برای اعتبارسنجی را در خود دارد، بنابراین نیازی به لایه ذخیرهسازی پایدار نیست.
-
مقاوم در برابر دستکاری: امضای JWT همیشه قبل از اعتماد به payload بررسی میشود.
-
انقضای قابل تنظیم: میتوان طول عمر کوتاهی تعیین کرد تا اگر توکن لو رفت، میزان خسارت محدود شود.
در این آموزش، یک سیستم احراز هویت امن در Go با استفاده از فریمورک Gin، توکنهای JWT و Redis ساخته میشود تا ذخیرهسازی توکن، چرخش (rotation) و ابطال (revocation) مدیریت شود.
معرفی JWT
JWT از چه چیزهایی تشکیل میشود
یک JWT از سه بخش Base64 URL-encoded تشکیل میشود که با نقطه به هم متصل شدهاند:
-
Header: نوع توکن و الگوریتم امضای استفادهشده. نوع توکن معمولاً “JWT” است و الگوریتم امضا میتواند HMAC یا SHA256 باشد.
-
Payload: بخش دوم توکن که شامل claimها است. این claimها شامل دادههای مخصوص اپلیکیشن (مثل user id، username)، زمان انقضای توکن (exp)، صادرکننده (iss)، موضوع (sub) و موارد دیگر میشود.
-
Signature: هدرِ کدگذاریشده، payload کدگذاریشده و یک secret که ارائه میشود برای ساخت امضا استفاده میشوند.
برای درک مفاهیم بالا از یک توکن ساده استفاده میشود. این توکن با کلید مخفی “my-secret-key” امضا شده است.
Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJ1c2VyX2lkIjo3fQ.bK73DLHVXVmtLXBYwl_TVLVf21OEJ0bzfZHXRbiAboA
Secret: my-secret-key
نگران نباشید، این توکن نامعتبر است و روی هیچ اپلیکیشن تولیدی کار نمیکند.
به JWT Debugger بروید و توکن را در بخش “Encoded” قرار دهید، secret را در بخش “Verify Signature” وارد کنید و بررسی کنید که عبارت “Signature Verified” نمایش داده شود.

انواع توکنها در این راهنما
از آنجا که JWT میتواند طوری تنظیم شود که بعد از یک مدت مشخص منقضی شود، در این اپلیکیشن دو نوع توکن در نظر گرفته میشود:
-
Access token: کوتاهمدت (حدود ~۱۵ دقیقه). در هر درخواست ارسال میشود.
-
Refresh token: بلندمدتتر (حدود ~۷ روز). از طریق یک endpoint اختصاصی با یک جفت جدید access+refresh تعویض میشود. چرخش (rotation) پیادهسازی میشود (refresh قدیمی بعد از استفاده نامعتبر میشود).
JWT را کجا ذخیره کنیم
برای یک اپلیکیشن وب تولیدی، بهشدت توصیه میشود JWTها در یک کوکی HttpOnly ذخیره شوند:

کوکیها با HttpOnly; Secure; SameSite=Lax (یا SameSite=None; Secure برای حالت cross-site) تنظیم میشوند و در API نیز CORS همراه با credentials فعال میشود.
انتخابهای مختلف الگوریتم
الگوریتمهای امضای JWT مشخص میکنند توکنها از نظر رمزنگاری چگونه امن میشوند. دو الگوریتمی که استفاده میشود:
-
HS256 (HMAC): یک secret مشترک؛ برای یک سرویس تکی ساده است (در این آموزش از HS256 استفاده میشود).
-
RS256 / EdDSA: کلیدهای نامتقارن؛ زمانی بهتر است که چند سرویس باید بدون اشتراکگذاری secret بتوانند اعتبارسنجی انجام دهند (همچنین یک مثال RS256 برای Vonage APIs نشان داده میشود).
نمای کلی پروژه
قبل از ورود به کد، باید مشخص شود چه چیزی ساخته میشود. یک API کوچک و امن برای یک اپلیکیشن فهرست کارها (to-do list) ساخته میشود. کاربران باید وارد شوند، کارهای شخصی خود را ببینند و خارج شوند. پیچیده نیست، اما باید آنقدر امن باشد که بتواند با اطلاعات واقعی کار کند.
در این آموزش، با Go و فریمورک Gin یک سیستم احراز هویت حداقلی مبتنی بر JWT ساخته میشود. برای سادهسازی، به پایگاه داده واقعی وصل نمیشود. بهجای آن، یک کاربر «نمونه» در کد تعریف میشود. مسیر /login یک نام کاربری و رمز عبور میگیرد، آن را با کاربر داخل حافظه بررسی میکند و دو توکن صادر میکند: یک access token کوتاهمدت و یک refresh token بلندمدتتر.
بعد از ورود، مسیر /todo فقط در صورتی پاسخ میدهد که یک access token معتبر ارسال شود. همچنین یک مسیر /logout برای ابطال فوری توکنها (با Redis) وجود دارد و مسیر /token/refresh برای چرخش refresh tokenها و زنده نگه داشتن سشنها بهشکل امن پیادهسازی میشود.
چرا Redis؟
JWTها معمولاً بدون وضعیت هستند. یعنی بعد از صدور، سرور نمیتواند آنها را «پس بگیرد». با ذخیره کردن jti (شناسه یکتا) در Redis همراه با Time to Live (TTL)، امکانهای زیر فراهم میشود:
-
ابطال فوری در زمان logout
-
چرخش refresh tokenها برای جلوگیری از استفاده مجدد
-
نگه داشتن سشنهای چند دستگاه بدون باطل کردن همه آنها
راهاندازی پروژه
شروع با ساخت و مقداردهی اولیه پروژه:
نصب وابستگیها:
-
gin: فریمورک HTTP سریع و سبک برای ساخت routeهای API
-
golang-jwt: ساخت، امضا و اعتبارسنجی JWT برای جریان احراز هویت
-
go-redis: کلاینت Redis برای Go، برای ذخیره و ابطال فوری توکنها
-
uuid: تولید شناسههای یکتای توکن (jti) برای هر JWT جهت ابطال دقیق
-
gin-contrib/cors: میانافزار برای تنظیم قواعد CORS جهت ارتباط امن کلاینت مرورگر با API
-
godotenv: بارگذاری متغیرهای محیطی از فایل .env
کدها و دستورات زیر دقیقاً همانطور که هستند باقی میمانند:
ساختار پروژه:
ایجاد فایلها:
افزودن متغیرهای محیطی
اپلیکیشن برای اجرای امن به چند مقدار محرمانه و تنظیمات نیاز دارد. این موارد در توسعه داخل فایل .env قرار میگیرند و هنگام استقرار بهصورت متغیر محیطی واقعی تعریف میشوند.
تولید secretهای قوی
دو کلید امضا لازم است: یکی برای access token و یکی برای refresh token. این کلیدها باید طولانی، تصادفی و غیرقابل حدس باشند. یک قانون خوب: حداقل ۳۲ بایت تصادفی بودن.
روی macOS / Linux:
روی Windows (PowerShell):
هر دستور یک رشته امن خروجی میدهد، مثلاً:
دستور مربوطه را دو بار اجرا کنید. یک مقدار برای ACCESS_SECRET و مقدار دیگر برای REFRESH_SECRET استفاده میشود.
افزودن متغیرهای ENV
در فایل .env موارد زیر اضافه میشود:
توضیح هر مورد:
-
ACCESS_SECRET: برای امضای access tokenهای کوتاهمدت (~۱۵ دقیقه)
-
REFRESH_SECRET: برای امضای refresh tokenهای بلندمدت (~۷ روز)
-
REDIS_ADDR: محل Redis را به اپلیکیشن اعلام میکند
-
FRONTEND_ORIGIN: آدرس فرانتاند مجاز برای CORS
Note: همیشه
.envرا به.gitignoreاضافه کنید تا secrets بهطور تصادفی وارد version control نشود.
اجرای Redis بهصورت محلی
برای استفاده از Redis در بخش بعد، لازم است Redis در پسزمینه اجرا شود.
macOS (Homebrew)
اگر Homebrew نصب است:
Windows
سادهترین راه استفاده از Docker (پیشنهادی) یا WSL است. بعد از نصب و اجرای Docker Desktop:
این کار آخرین ایمیج Redis 7 را دریافت میکند، در پسزمینه اجرا میکند و روی پورت ۶۳۷۹ در دسترس قرار میدهد.
تنظیم Redis
این بخش یک wrapper کوچک پیرامون کلاینت Redis میسازد تا اپلیکیشن بتواند ساده و تمیز با Redis تعامل کند. با تعریف یک struct به نام Redis که یک *redis.Client را نگه میدارد، روشی قابل استفاده مجدد برای مدیریت اتصال Redis در سراسر اپ ایجاد میشود. این کد پکیج رسمی go-redis v9 را ایمپورت میکند.
در اینجا، فیلد Client یک اتصال زنده Redis را نگه میدارد که در بخش دیگری از کد پیکربندی شده است. با قرار دادن این قسمت در پکیج جداگانه (store)، منطق ذخیرهسازی از بقیه اپ جدا میشود و نگهداری و تست سادهتر خواهد شد.
کد زیر بدون هیچ تغییری همانطور که هست باقی میماند:
تولید احراز هویت JWT با Go
صدور و ذخیرهسازی توکنها
در این مرحله access tokenهای کوتاهمدت و refresh tokenهای بلندمدت با استفاده از claimهای ثبتشده استاندارد JWT مانند issuer، audience، subject و expiration ساخته میشوند. پکیج golang-jwt فرآیند امضا را مدیریت میکند و با استفاده از secretهای امن ذخیرهشده، مانع دستکاری توکنها میشود. پس از تولید، هر دو توکن در Redis ذخیره میشوند تا بعداً امکان اعتبارسنجی یا ابطال آنها فراهم شود. این کار یک روش امن و متمرکز برای مدیریت سشنهای فعال ایجاد میکند.
محافظت از مسیرها با Middleware
این مرحله تضمین میکند تنها درخواستهای کاربران احراز هویتشده به برخی endpointها دسترسی دارند. middleware هر درخواست را رهگیری میکند، وجود یک access token معتبر را بررسی میکند و همچنین چک میکند که توکن در Redis باطل نشده باشد.
اگر توکن همه بررسیها را پاس کند، درخواست به handler مقصد میرود؛ در غیر این صورت با خطای مناسب مسدود میشود. این کار منطق احراز هویت را متمرکز میکند تا لازم نباشد در هر route بررسی توکن تکرار شود و در نتیجه کد تمیزتر و امنتر میشود.
کد زیر بدون هیچ تغییری همانطور که هست باقی میماند:
یک مسیر محافظتشده نمونه: /todo
این مسیر نشان میدهد یک endpoint احراز هویتشده چگونه کار میکند. handler مربوط به Create مقدار userID را از توکن اعتبارسنجیشده (که توسط middleware تنظیم شده) میخواند و آن را با payload مربوط به todo ترکیب میکند. بهجای ذخیره در پایگاه داده، همین داده بهصورت JSON برگردانده میشود تا بتوان بررسی کرد احراز هویت درست کار میکند. در یک اپلیکیشن واقعی، اینجا همان جایی است که منطق ذخیره todo در دیتابیس اضافه میشود.
کد زیر بدون هیچ تغییری همانطور که هست باقی میماند:
اتصال همه بخشها به هم
در مرحله نهایی، همه اجزا (ذخیرهساز Redis، منطق احراز هویت، middleware و handlerهای route) در یک اپلیکیشن Gin قابل اجرا کنار هم قرار میگیرند. فایل main.go وابستگیها را مقداردهی میکند، routeها را تنظیم میکند و سرور HTTP را بالا میآورد. این همان چسبی است که تمام بخشهای اپ را به هم متصل میکند تا احراز هویت، ذخیره توکن و routeهای محافظتشده از ابتدا تا انتها درست کار کنند.
تست احراز هویت JWT با cURL
حالا که اپلیکیشن و Redis در حال اجرا هستند، چرخه کامل احراز هویت بررسی میشود: ورود، ساخت todo، خروج، شکست خوردن یک درخواست احراز هویتشده، و refresh کردن توکنها.
ابتدا سرور Gin اجرا میشود:
اگر همهچیز درست باشد، Gin routeها را چاپ میکند و روی :۸۰۸۰ گوش میدهد.
تست اپلیکیشن Gin
اطمینان حاصل شود که هر مرحله در یک تب جدا از تب سرور اجرا میشود.
تست ۱: ورود و ذخیره کوکیها
این مرحله ورود کاربر را شبیهسازی میکند و access_token و refresh_token را برای درخواستهای بعدی در cookies.txt ذخیره میکند.
خروجی مورد انتظار:
-
HTTP 200 OK
-
دو هدر Set-Cookie (access_token, refresh_token)
-
JSON: {“ok”:true}
تست ۲: ساخت یک Todo (احراز هویتشده)
خروجی مورد انتظار:
-
HTTP 201 Created
-
JSON شامل user_id و title
تست ۳: خروج (ابطال توکنها)
خروجی مورد انتظار:
-
HTTP 200 OK
-
کوکیهای access_token و refresh_token خالی میشوند و Max-Age=0 میگیرند.
تست ۴: تلاش مجدد (باید شکست بخورد)
حالا که توکنها باطل شدهاند، یک درخواست ساخت باید شکست بخورد:
خروجی مورد انتظار:
-
HTTP 401 Unauthorized
-
JSON: {“error”:”token revoked”}
تست ۵: Refresh توکنها (گرفتن یک جفت جدید)
این تست بعد از ورود دوباره انجام میشود.
خروجی مورد انتظار:
-
HTTP 201 Created
-
Set-Cookie جدید برای هر دو توکن
-
JSON: {“ok”:true}
جمعبندی
یک جریان کامل و مناسب تولید برای احراز هویت JWT در Go با Gin ساخته شد. با صدور access tokenهای کوتاهمدت، چرخش refresh tokenها، ذخیره امن آنها در کوکیهای HttpOnly و ابطال فوری با Redis، امنیت واقعی در یک اپ Go ایجاد میشود. همچنین middleware وجود دارد که هم از کوکیها و هم از Bearer tokenها پشتیبانی میکند، بهاضافه یک مکانیزم refresh برای زنده نگه داشتن سشن بدون اجبار به ورود مجدد.
با این پایه، میتوان بهسادگی کارهای زیر را انجام داد:
-
جایگزینی کاربر نمونه داخل حافظه با جستوجوی واقعی در پایگاه داده
-
پیادهسازی ردیابی refresh token بهازای هر دستگاه برای کنترل دقیقتر سشنها
-
تغییر به امضای RS256 یا EdDSA برای اعتبارسنجی با کلید عمومی بین سرویسها
-
ادغام با APIهای خارجی تا پس از برخی اکشنها تماس یا پیام ارسال شود
JWT فقط برای احراز هویت نیست؛ یک بلوک سازنده برای سیستمهای امن و بدون وضعیت است که قابلیت مقیاسپذیری دارند. با ترکیب آن با Redis برای ابطال و طراحی دقیق توکن، راهکاری ساخته میشود که بین کارایی، امنیت و بهرهوری توسعهدهنده تعادل ایجاد میکند.
اگر این رویکرد در پروژهای استفاده شود یا با احراز هویت چندمرحلهای، OAuth یا ادغامهای API توسعه داده شود، امکان اشتراک تجربه و نتایج وجود دارد.
