اول از همه بگم: JWT مخفف JSON Web Token و JWS مخفف JSON Web Signature و JWE مخفف JSON Web Encryption هست.
تو فارسی به jwt میگم جوت تا تایپ و تلفظش ساده باشه.
یه جاهایی جمله با jwt شروع میشه و ویرگول نوشته چپ به راست میکنه، مجبورم.
دو روش کلی و پرکاربرد اعتبارسنجی سمت سرور، برای برنامههای سمت کاربر وب وجود دارند:
مزیتهای استفادهی از روش مبتنی بر توکن چیست؟
اما JWT یا JSON Web Token چیست؟
توضیح: JSON Web Token یا JWT یک استاندارد وب است (RFC 7519) که روشی فشرده و خود شمول (self-contained) را جهت انتقال امن اطلاعات بین مقاصد مختلف را توسط یک شی JSON تعریف میکند. این اطلاعات قابل تصدیق و اطمینان هستند، از این رو که به صورت دیجیتال امضا میشوند. JWTها توسط یک کلید خصوصی (با استفاده از الگوریتم HMAC) و یا یک جفت کلید خصوصی و عمومی (توسط الگوریتم RSA) قابل امضا شدن هستند.
در این تعریف واژههایی مانند «فشرده» و «خود شمول» بکار رفتهاند:
- «فشرده بودن»: اندازهی شی JSON یک توکن در این حالت کوچک بوده و به سادگی از طریق یک URL و یا پارامترهای POST و یا داخل یک HTTP Header قابل ارسال است و به دلیل کوچک بودن این اندازه انتقال آن نیز سریع است.
- «خود شمول»: بار مفید (payload) این توکن شامل تمام اطلاعات مورد نیاز جهت اعتبارسنجی یک کاربر است تا دیگر نیازی به کوئری گرفتن هر باره از بانک اطلاعاتی نباشد (در این روش مرسوم است که فقط یکبار از بانک اطلاعاتی کوئری گرفته شده و اطلاعات مرتبط با کاربر را امضای دیجیتال کرده و به سمت کاربر ارسال میکنند).
جوتها دارای سه قسمت است و این قسمتها با نقطه از هم جا شدند، مانند: xxxxx.yyyyy.zzzzz که شامل سه بخش: header.payload.signature میباشند و هر بخش با الگوریتم Base64 اینکد میشود. نمونهای از یک توکن جوت:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o
بخش Header:
بخش Header عموما دارای دو قسمت است که نوع توکن و الگوریتم مورد استفاده توسط آن را مشخص میکند. نوع توکن در اینجا JWT است و الگوریتمهای مورد استفاده، عموما HMAC SHA256 و یا RSA هستند. نمونه اطلاعات دیکد شده بخش هدر:
{ "alg": "RS256", "typ": "JWT" }
بخش payload:
بخش payload یا «بار مفید» توکن، شامل claims است، منظور از claims اطلاعاتی است در مورد موجودیت مدنظر (عموما کاربر) و یک سری متادیتای اضافی. سه نوع claim وجود دارند:
نوع اول Reserved claims: یک سری اطلاعات مفید و از پیش تعیین شده غیراجباری هستند مانند:iss یا صادر کنند، exp یا تاریخ انقضا، sub یا عنوان (subject) و aud یا مخاطب (audience)
نوع دوم Public claims: میتواند شامل اطلاعاتی باشد که توسط IANA JSON Web Token Registry پیشتر ثبت شدهاست و فضاهای نام آنها تداخلی نداشته باشند.
نوع سوم Private claims: ادعای سفارشی هستند که جهت انتقال دادهها بین مقاصد مختلف مورد استفاده قرار میگیرند.
نمونه اینکد شده بخش payload توکن:
{ "exp": 1520507268, "sub": 582946, "name": "ErFUN KH", "admin": true }
در نمونه بالا دو دیتای اول از نوع Reserved claims میباشد و دو دیتای آخر از نوع Private claims.
به طور استاندارد بخش Reserved claims میتونه شامل این موارد باشه:
این مقادیر همینجا تموم نمیشه، میتونید نگاهی به لیست مایکروسافت کنید، ولی بهتره خیلی شلوغش نکنید، مهم همینها هستند که خیلی وقتها استفاده نمیشن نهایتا sub و exp استفاده میکنند.
بخش signature:
تا اینا دیدیم همه دیتاهای با الگوریتم بیس۶۴ اینکد شد که به راحتی تو هرسیستمی دیکد میشه (اگه با این شیوه آشنا نیستید باید بگم فقط کاراکترهای غیر مجاز به حروف تبدیل میکنه، مقاله ویکیپدیا بخونید) و هرکسی میتونه همچین توکنی بسازه و برای ما ارسال کنه ولی نه با وجود بخش سوم (امضا).
همونطوری که میبینید بخش اول (هدر) با بخش دوم (پیلود) جمع شده و بعد با کلید خصوصی (کلید خصوصی فقط در سرور موجوده) رمز میشه، این به عنوان بخش سوم یا امضا توکن ما استفاده میشه، درصورتی که اطاعات پیلود دست کاری بشه امضا برای سرور معتبر نیست، همچنین چون کاربران کلید خصوصی ندارند نمیتونند خودشون توکن تولید کنند.
همینطور درنظر داسته باشید اطلاعات حساس مثل پسورد یا... داخل توکن نذارید چون هرکسی میتونه به سادگی توکن باز کنه و دیتای داخلش بخونه، ولی اگر واقعا نیازه اطلاعات حساس داخلش بذارید باید از JWE استفاده کنید.
چگونه از JWT در برنامهها استفاده میشود؟
زمانیکه کاربر، لاگین موفقی را به سیستم انجام میدهد، یک توکن امن توسط سرور صادر شده و با فرمت JWT به سمت کلاینت ارسال میشود. این توکن باید به صورت محلی در سمت کاربر ذخیره شود. عموما از local storage برای ذخیرهی این توکن استفاده میشود اما استفادهی از کوکیها نیز منعی ندارد (ولی از لحاظ امنیتی خیلی خوب نیست، پایینتر توضیح میدم چرا). بنابراین دیگر در اینجا سشنی در سمت سرور به ازای هر کاربر ایجاد نمیشود و کوکی سمت سروری به سمت کلاینت ارسال نمیگردد.
سپس هر زمانیکه کاربری قصد داشت به یک صفحه یا محتوای محافظت شده دسترسی پیدا کند، باید توکن خود را به سمت سرور ارسال نماید. عموما اینکار توسط یک header سفارشی Authorization به همراه Bearer schema صورت میگیرد و یک چنین شکلی را دارد:
Authorization: Bearer <token>
این روش اعتبارسنجی، بدون حالت (stateless) است، از این جهت که وضعیت کاربر هیچگاه در سمت سرور ذخیره نمیگردد. API سمت سرور ابتدا به دنبال هدر Authorization فوق در درخواست دریافتی میگردد، اگر یافت شد و اصالت آن تایید شد کاربر امکان دسترسی به منبع محافظت شده را پیدا میکند. نکتهی مهم اینجا است که چون این توکنها «خود شمول» هستند و تمام اطلاعات لازم جهت اعطای دسترسیهای کاربر به او در آن وجود دارند، دیگر نیازی به رفت و برگشت به بانک اطلاعاتی جهت تایید این اطلاعات تصدیق شده نیست. به همین جهت کارآیی و سرعت بالاتری را نیز به همراه خواهند داشت.
نگاهی به محل ذخیره سازی JWT و نکات مرتبط با آن
محل متداول ذخیره JWT ها در local storage مرورگرها است و در اغلب سناریوها نیز به خوبی کار میکند. فقط باید دقت داشت که local storage یک sandbox است و محدود به دومین جاری برنامه و از طریق برای مثال زیر دامنههای آن قابل دسترسی نیست. در این حالت میتوان JWT را در کوکیهای ایجاد شده در سمت کاربر نیز ذخیره کرد که چنین محدودیتی را ندارند. اما باید دقت داشت که حداکثر اندازهی حجم کوکیها 4 کیلوبایت است و با افزایش claims ذخیره شدهی در یک JWT و انکد شدن آن، این حجم ممکن است از 4 کیلوبایت بیشتر شود. بنابراین باید به این نکات دقت داشت.
امکان ذخیره سازی توکنها در session storage مرورگرها نیز وجود دارد. session storage بسیار شبیه است به local storage اما به محض بسته شدن مرورگر پاک میشود.
اگر از local storage استفاده میکنید حملات Cross Site Request Forgery در اینجا دیگر موثر نخواهند بود، اما اگر به حالت استفادهی از کوکیها برای ذخیرهی توکنها سوئیچ کنید این مساله همانند قبل خواهد بود و مسیر است، در این حالت بهتر است طول عمر توکنها را تاحد ممکن کوتاه تعریف کنید تا اگر اطلاعات آنها فاش شد به زودی بیمصرف شوند.
انقضا و صدور مجدد توکنها به چه صورتی است؟
توکنهای بدون حالت صرفا بر اساس بررسی امضای پیام رسیده کار میکنند. به این معنا که یک توکن میتواند تا ابد معتبر باقی بماند. برای رفع این مشکل باید exp یا تاریخ انقضای متناسبی را به توکن اضافه کرد. برای برنامههای حساس این عدد میتواند 15 دقیقه باشد و برای برنامههای کمتر حساس، چندین ماه.
اما اگر در این بین قرار به ابطال سریع توکنی بود چه باید کرد؟ (مثلا کاربری را در همین لحظه غیرفعال کردید)
یک راه حل آن، ثبت رکوردهای تمام توکنهای صادر شده در بانک اطلاعاتی است. برای این منظور میتوان یک از فیلد jti کرد. این idها در بانک اطلاعاتی ذخیره میکنیم. به این ترتیب میتوان بین توکنهای صادر شده و کاربران و اطلاعات به روز آنها ارتباط برقرار کرد. در این حالت برنامه علاوه بر بررسی امضای توکن میتواند به لیست idهای صادر شده و ذخیره شدهی در دیتابیس نیز مراجعه کرده و اعتبارسنجی اضافهتری را جهت باطل کردن سریع توکنها انجام دهد. هرچند این روش دیگر آنچنان stateless نیست، اما با دنیای واقعی سازگاری بیشتری دارد.
حداکثر امنیت JWTها را چگونه میتوان تامین کرد؟
- تمام توکنهای خود را با یک کلید قوی امضا کنید و این کلید تنها باید بر روی سرور ذخیره شده باشد. هر زمانیکه سرور توکنی را از کاربر دریافت میکند، این سرور است که باید کار بررسی اعتبار امضای پیام رسیده را بر اساس کلید قوی خود انجام دهد.
- اگر اطلاعات حساسی را در توکنها قرار میدهید، باید از JWE یا JSON Web Encryption استفاده کنید، زیرا JWTها صرفا دارای امضای دیجیتال هستند و نه اینکه رمزنگاری شده باشند.
- بهتر است توکنها را از طریق ارتباطات غیر HTTPS، ارسال نکرد.
- اگر از کوکیها برای ذخیره سازی آنها استفاده میکنید، از Secure استفاده کنید تا از Cross-Site Scripting XSS attacks در امان باشید.
- مدت اعتبار توکنهای صادر شده را منطقی انتخاب کنید.