تعليمي JSON Web Token: مثال في Laravel و AngularJS

نشرت: 2022-03-11

مع تزايد شعبية تطبيقات الصفحة الواحدة وتطبيقات الهاتف المحمول وخدمات RESTful API ، تغيرت الطريقة التي يكتب بها مطورو الويب التعليمات البرمجية الخلفية بشكل ملحوظ. باستخدام تقنيات مثل AngularJS و BackboneJS ، لم نعد ننفق الكثير من الوقت في إنشاء العلامات ، وبدلاً من ذلك نقوم ببناء واجهات برمجة التطبيقات التي تستهلكها تطبيقاتنا الأمامية. تتعلق نهايتنا الخلفية بمنطق الأعمال والبيانات ، بينما يتم نقل منطق العرض التقديمي حصريًا إلى تطبيقات الهاتف المحمول أو الواجهة الأمامية. أدت هذه التغييرات إلى طرق جديدة لتنفيذ المصادقة في التطبيقات الحديثة.

المصادقة هي أحد أهم أجزاء أي تطبيق ويب. لعقود من الزمان ، كانت ملفات تعريف الارتباط والمصادقة المستندة إلى الخادم هي الحل الأسهل. ومع ذلك ، قد يكون التعامل مع المصادقة في تطبيقات الجوال والصفحة الواحدة الحديثة أمرًا صعبًا ويتطلب نهجًا أفضل. أفضل الحلول المعروفة لمشاكل المصادقة لواجهات برمجة التطبيقات هي OAuth 2.0 و JSON Web Token (JWT).

قبل الدخول في هذا البرنامج التعليمي لـ JSON Web Token ، ما هو JWT بالضبط؟

ما هو رمز الويب JSON؟

يتم استخدام JSON Web Token لإرسال المعلومات التي يمكن التحقق منها والموثوق بها عن طريق التوقيع الرقمي. وهو يتألف من كائن JSON مضغوط وآمن لعناوين URL ، ويتم توقيعه بشكل مشفر للتحقق من أصالته ، ويمكن أيضًا تشفيره إذا كانت الحمولة تحتوي على معلومات حساسة.

نظرًا لبنيتها المدمجة ، تُستخدم JWT عادةً في رؤوس Authorization HTTP أو معامِلات استعلام URL.

هيكل JSON Web Token

يتم تمثيل JWT كسلسلة من قيم base64url المشفرة المفصولة بأحرف الفترة.

مثال على رمز الويب JSON في Laravel و angularjs

فيما يلي مثال على رمز JWT المميز:

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0 . yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

رأس

يحتوي العنوان على البيانات الوصفية للرمز المميز ويحتوي على الحد الأدنى من نوع التوقيع وخوارزمية التشفير. (يمكنك استخدام أداة تنسيق JSON لتجميل كائن JSON.)

مثال على العنوان

 { "alg": "HS256", "typ": "JWT" }

يوضح مثال رأس JWT هذا أن الكائن المشفر هو رمز ويب JSON ، وأنه تم توقيعه باستخدام خوارزمية HMAC SHA-256.

بمجرد أن يتم تشفير هذا Base64 ، يكون لدينا الجزء الأول من JWT الخاص بنا.

 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

الحمولة (المطالبات)

في سياق JWT ، يمكن تعريف المطالبة على أنها بيان حول كيان (عادةً ، المستخدم) ، بالإضافة إلى بيانات وصفية إضافية حول الرمز المميز نفسه. تحتوي المطالبة على المعلومات التي نريد نقلها ، والتي يمكن للخادم استخدامها للتعامل بشكل صحيح مع مصادقة JSON Web Token. هناك مطالبات متعددة يمكننا تقديمها ؛ وتشمل هذه أسماء المطالبات المسجلة وأسماء المطالبات العامة وأسماء المطالبات الخاصة.

مطالبات JWT المسجلة

هذه هي المطالبات المسجلة في سجل IANA JSON Web Token Claims. لا يُقصد من مطالبات JWT أن تكون إلزامية بل لتوفير نقطة انطلاق لمجموعة من المطالبات المفيدة والقابلة للتشغيل المتبادل.

وتشمل هذه:

  • الإصدار : مُصدر الرمز المميز
  • sub : موضوع الرمز المميز
  • aud : جمهور الرمز المميز
  • exp : وقت انتهاء الصلاحية JWT المحدد في وقت Unix
  • nbf : الوقت "ليس قبل" الذي يحدد الوقت الذي قبله يجب عدم قبول JWT للمعالجة
  • iat : "تم الإصدار في" وقت إصدار الرمز المميز ، في وقت Unix
  • jti : توفر مطالبة معرف JWT معرفًا فريدًا لـ JWT

المطالبات العامة

يجب أن يكون للمطالبات العامة أسماء مقاومة للتصادم. بجعل الاسم URI أو URN ، يتم تجنب تضارب التسمية في JWTs حيث لا يكون المرسل والمستقبل جزءًا من شبكة مغلقة.

مثال على اسم المطالبة العامة يمكن أن يكون: https://www.toptal.com/jwt_claims/is_admin ، وأفضل ممارسة هي وضع ملف في ذلك الموقع يصف المطالبة بحيث يمكن إلغاء توثيقه من أجل التوثيق.

المطالبات الخاصة

يمكن استخدام أسماء المطالبات الخاصة في الأماكن التي يتم فيها تبادل JWTs فقط في بيئة مغلقة بين الأنظمة المعروفة ، مثل داخل مؤسسة. هذه ادعاءات يمكننا تعريف أنفسنا بها ، مثل معرفات المستخدم أو أدوار المستخدم أو أي معلومات أخرى.

استخدام أسماء المطالبات التي قد يكون لها معاني دلالية متضاربة خارج نظام مغلق أو خاص عرضة للتصادم ، لذا استخدمها بحذر.

من المهم ملاحظة أننا نريد الاحتفاظ برمز ويب صغير قدر الإمكان ، لذلك استخدم فقط البيانات الضرورية داخل المطالبات العامة والخاصة.

مثال الحمولة الصافية JWT

 { "iss": "toptal.com", "exp": 1426420800, "https://www.toptal.com/jwt_claims/is_admin": true, "company": "Toptal", "awesome": true }

تحتوي حمولة المثال هذه على مطالبتين مسجلتين ، مطالبة عامة واحدة ومطالبتين خاصتين. بمجرد أن يتم ترميزها باستخدام base64 ، يكون لدينا الجزء الثاني من JWT الخاص بنا.

 eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0

التوقيع

يتبع معيار JWT مواصفات JSON Web Signature (JWS) لإنشاء الرمز المميز النهائي الموقّع. يتم إنشاؤه من خلال الجمع بين رأس JWT المشفر وحمولة JWT المشفرة ، وتوقيعه باستخدام خوارزمية تشفير قوية ، مثل HMAC SHA-256. يحتفظ الخادم بالمفتاح السري للتوقيع حتى يتمكن من التحقق من الرموز المميزة الحالية وتوقيع رموز جديدة.

 $encodedContent = base64UrlEncode(header) + "." + base64UrlEncode(payload); $signature = hashHmacSHA256($encodedContent);

هذا يعطينا الجزء الأخير من JWT.

 yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw

أمن وتشفير JWT

من الضروري استخدام TLS / SSL جنبًا إلى جنب مع JWT ، لمنع هجمات man-in-the-middle. في معظم الحالات ، سيكون هذا كافيًا لتشفير حمولة JWT إذا كانت تحتوي على معلومات حساسة. ومع ذلك ، إذا أردنا إضافة طبقة حماية إضافية ، فيمكننا تشفير حمولة JWT نفسها باستخدام مواصفات JSON Web Encryption (JWE).

بالطبع ، إذا أردنا تجنب النفقات الإضافية لاستخدام JWE ، فإن الخيار الآخر هو ببساطة الاحتفاظ بالمعلومات الحساسة في قاعدة البيانات الخاصة بنا ، واستخدام الرمز المميز الخاص بنا لاستدعاءات API إضافية للخادم كلما احتجنا إلى الوصول إلى البيانات الحساسة.

لماذا الحاجة إلى رموز الويب؟

قبل أن نتمكن من رؤية جميع مزايا استخدام مصادقة JWT ، علينا أن ننظر إلى الطريقة التي تم بها إجراء المصادقة في الماضي.

المصادقة المستندة إلى الخادم

المصادقة المستندة إلى الخادم

نظرًا لأن بروتوكول HTTP عديم الحالة ، يجب أن تكون هناك آلية لتخزين معلومات المستخدم وطريقة لمصادقة المستخدم في كل طلب لاحق بعد تسجيل الدخول. تستخدم معظم مواقع الويب ملفات تعريف الارتباط لتخزين معرف جلسة المستخدم.

كيف تعمل

يقوم المتصفح بإجراء طلب POST إلى الخادم الذي يحتوي على هوية المستخدم وكلمة المرور. يستجيب الخادم بملف تعريف ارتباط ، يتم تعيينه على متصفح المستخدم ، ويتضمن معرّف جلسة لتحديد هوية المستخدم.

في كل طلب لاحق ، يحتاج الخادم إلى العثور على تلك الجلسة وإلغاء تسلسلها ، لأنه يتم تخزين بيانات المستخدم على الخادم.

عيوب المصادقة المستندة إلى الخادم

  • صعب القياس : يحتاج الخادم إلى إنشاء جلسة للمستخدم واستمرارها في مكان ما على الخادم. يمكن القيام بذلك في الذاكرة أو في قاعدة البيانات. إذا كان لدينا نظام موزع ، فعلينا التأكد من أننا نستخدم تخزين جلسة منفصل غير مقترن بخادم التطبيق.

  • مشاركة الطلبات عبر الأصل (CORS) : عند استخدام استدعاءات AJAX لجلب مورد من مجال آخر ("عبر الأصل") قد نواجه مشكلات مع الطلبات المحظورة لأن طلبات HTTP ، افتراضيًا ، لا تتضمن ملفات تعريف الارتباط في طلبات الأصل.

  • الاقتران بإطار عمل الويب : عند استخدام المصادقة المستندة إلى الخادم ، فإننا مرتبطون بنظام المصادقة الخاص بإطار عملنا. من الصعب حقًا ، بل من المستحيل ، مشاركة بيانات الجلسة بين أطر الويب المختلفة المكتوبة بلغات برمجة مختلفة.

المصادقة القائمة على الرمز المميز

المصادقة القائمة على الرمز المميز

المصادقة القائمة على الرمز / JWT عديمة الحالة ، لذلك ليست هناك حاجة لتخزين معلومات المستخدم في الجلسة. يمنحنا هذا القدرة على توسيع نطاق تطبيقنا دون القلق من المكان الذي قام فيه المستخدم بتسجيل الدخول. يمكننا بسهولة استخدام نفس الرمز المميز لجلب مورد آمن من مجال غير المجال الذي تم تسجيل الدخول إليه.

كيف تعمل رموز الويب JSON

يقوم المتصفح أو العميل المحمول بتقديم طلب إلى خادم المصادقة يحتوي على معلومات تسجيل دخول المستخدم. ينشئ خادم المصادقة رمز وصول JWT جديدًا ويعيده إلى العميل. في كل طلب إلى مورد مقيد ، يرسل العميل رمز الوصول في سلسلة الاستعلام أو رأس Authorization . يقوم الخادم بعد ذلك بالتحقق من صحة الرمز المميز ، وإذا كان صالحًا ، فسيعيد المورد الآمن إلى العميل.

يمكن لخادم المصادقة توقيع الرمز المميز باستخدام أي طريقة توقيع آمنة. على سبيل المثال ، يمكن استخدام خوارزمية مفتاح متماثل مثل HMAC SHA-256 إذا كانت هناك قناة آمنة لمشاركة المفتاح السري بين جميع الأطراف. بدلاً من ذلك ، يمكن أيضًا استخدام نظام غير متماثل ذي مفتاح عمومي ، مثل RSA ، مما يلغي الحاجة إلى مزيد من مشاركة المفاتيح.

مزايا المصادقة القائمة على الرمز المميز

عديم الحالة ، أسهل في القياس : يحتوي الرمز المميز على جميع المعلومات لتحديد هوية المستخدم ، مما يلغي الحاجة إلى حالة الجلسة. إذا استخدمنا موازن تحميل ، فيمكننا تمرير المستخدم إلى أي خادم ، بدلاً من الارتباط بنفس الخادم الذي قمنا بتسجيل الدخول إليه.

قابلية إعادة الاستخدام : يمكن أن يكون لدينا العديد من الخوادم المنفصلة ، تعمل على منصات ومجالات متعددة ، وإعادة استخدام نفس الرمز المميز لمصادقة المستخدم. من السهل إنشاء تطبيق يشارك الأذونات مع تطبيق آخر.

أمان JWT : نظرًا لأننا لا نستخدم ملفات تعريف الارتباط ، فلا داعي للحماية من هجمات التزوير عبر المواقع (CSRF). لا يزال يتعين علينا تشفير الرموز المميزة الخاصة بنا باستخدام JWE إذا كان علينا وضع أي معلومات حساسة فيها ، ونقل الرموز المميزة الخاصة بنا عبر HTTPS لمنع هجمات man-in-the-middle.

الأداء : لا يوجد بحث من جانب الخادم للعثور على الجلسة وإلغاء تسلسلها في كل طلب. الشيء الوحيد الذي يتعين علينا القيام به هو حساب HMAC SHA-256 للتحقق من صحة الرمز المميز وتحليل محتواه.

مثال على رمز ويب JSON باستخدام Laravel 5 و AngularJS

في هذا البرنامج التعليمي لـ JWT ، سأشرح كيفية تنفيذ المصادقة الأساسية باستخدام JSON Web Tokens في تقنيتين شائعتين للويب: Laravel 5 لكود الواجهة الخلفية و AngularJS لمثال تطبيق الصفحة الواحدة (SPA) للواجهة الأمامية. (يمكنك العثور على العرض التوضيحي بالكامل هنا ، وكود المصدر في مستودع GitHub هذا حتى تتمكن من متابعة البرنامج التعليمي.)

لن يستخدم مثال رمز الويب JSON هذا أي نوع من التشفير لضمان سرية المعلومات المنقولة في المطالبات. من الناحية العملية ، غالبًا ما يكون هذا جيدًا ، لأن TLS / SSL يشفر الطلب. ومع ذلك ، إذا كان الرمز المميز سيحتوي على معلومات حساسة ، مثل رقم الضمان الاجتماعي للمستخدم ، فيجب أيضًا تشفيره باستخدام JWE.

مثال Laravel Backend

سنستخدم Laravel للتعامل مع تسجيل المستخدم ، واستمرار بيانات المستخدم في قاعدة بيانات وتوفير بعض البيانات المقيدة التي تحتاج إلى مصادقة لتطبيق Angular للاستهلاك. سننشئ مثالًا للنطاق الفرعي لواجهة برمجة التطبيقات لمحاكاة مشاركة الموارد عبر الأصل (CORS) أيضًا.

التثبيت والمشاريع التمهيدية

من أجل استخدام Laravel ، علينا تثبيت مدير حزم Composer على أجهزتنا. عند التطوير في Laravel ، أوصي باستخدام "صندوق" Vagrant المعبأ مسبقًا من Laravel Homestead. يوفر لنا بيئة تطوير كاملة بغض النظر عن نظام التشغيل الخاص بنا.

أسهل طريقة لتمهيد تطبيق JWT Laravel الخاص بنا هي استخدام حزمة Composer Laravel Installer.

 composer global require "laravel/installer=~1.1"

الآن نحن جميعًا جاهزون لإنشاء مشروع Laravel جديد عن طريق تشغيل laravel new jwt .

لأية أسئلة حول هذه العملية ، يرجى الرجوع إلى توثيق Laravel الرسمي.

بعد إنشاء تطبيق Laravel 5 الأساسي ، نحتاج إلى إعداد Homestead.yaml الخاص بنا ، والذي سيقوم بتهيئة تعيينات المجلدات وتكوين المجالات لبيئتنا المحلية.

مثال على ملف Homestead.yaml :

 --- ip: "192.168.10.10" memory: 2048 cpus: 1 authorize: /Users/ttkalec/.ssh/public.psk keys: - /Users/ttkalec/.ssh/private.ppk folders: - map: /coding/jwt to: /home/vagrant/coding/jwt sites: - map: jwt.dev to: /home/vagrant/coding/jwt/public - map: api.jwt.dev to: /home/vagrant/coding/jwt/public variables: - key: APP_ENV value: local

بعد أن قمنا بتشغيل مربع Vagrant الخاص بنا باستخدام الأمر vagrant up وتسجيل الدخول إليه باستخدام vagrant ssh ، ننتقل إلى دليل المشروع المحدد مسبقًا. في المثال أعلاه سيكون هذا /home/vagrant/coding/jwt . يمكننا الآن تشغيل الأمر php artisan migrate لإنشاء جداول المستخدم الضرورية في قاعدة البيانات الخاصة بنا.

تثبيت تبعيات المؤلف

لحسن الحظ ، هناك مجتمع من المطورين يعملون على Laravel ويحتفظون بالعديد من الحزم الرائعة التي يمكننا إعادة استخدامها وتوسيع تطبيقنا بها. في هذا المثال ، سنستخدم tymon/jwt-auth ، بواسطة Sean Tymon ، للتعامل مع الرموز المميزة على جانب الخادم ، و barryvdh/laravel-cors ، بواسطة Barry vd. Heuvel ، للتعامل مع CORS.

جي دبليو تي أووث

اطلب حزمة tymon/jwt-auth في composer.json الخاص بنا وقم بتحديث تبعياتنا.

 composer require tymon/jwt-auth 0.5.*

أضف JWTAuthServiceProvider إلى مصفوفة موفري app/config/app.php .

 'Tymon\JWTAuth\Providers\JWTAuthServiceProvider'

بعد ذلك ، في ملف app/config/app.php ، ضمن مصفوفة aliases ، نضيف واجهة JWTAuth .

 'JWTAuth' => 'Tymon\JWTAuth\Facades\JWTAuth'

أخيرًا ، سنرغب في نشر تهيئة الحزمة باستخدام الأمر التالي: php artisan config: publish tymon / jwt-auth

يتم تشفير الرموز المميزة لـ JSON Web باستخدام مفتاح سري. يمكننا إنشاء هذا المفتاح باستخدام الأمر php artisan jwt:generate . سيتم وضعه داخل ملف config/jwt.php . ومع ذلك ، في بيئة الإنتاج ، لا نريد أبدًا أن يكون لدينا كلمات مرور أو مفاتيح API داخل ملفات التكوين. بدلاً من ذلك ، يجب علينا وضعها داخل متغيرات بيئة الخادم والإشارة إليها في ملف التكوين باستخدام وظيفة env . علي سبيل المثال:

 'secret' => env('JWT_SECRET')

يمكننا معرفة المزيد حول هذه الحزمة وجميع إعدادات التكوين الخاصة بها على Github.

Laravel-cors

اطلب الحزمة barryvdh/laravel-cors في composer.json وقم بتحديث تبعياتنا.

 composer require barryvdh/laravel-cors 0.4.x@dev

أضف CorsServiceProvider إلى مجموعة موفري app/config/app.php .

 'Barryvdh\Cors\CorsServiceProvider'

ثم أضف البرمجيات الوسيطة إلى app/Http/Kernel.php .

 'Barryvdh\Cors\Middleware\HandleCors'

انشر التكوين إلى ملف config/cors.php باستخدام الأمر php artisan vendor:publish .

مثال على تكوين ملف cors.php :

 return [ 'defaults' => [ 'supportsCredentials' => false, 'allowedOrigins' => [], 'allowedHeaders' => [], 'allowedMethods' => [], 'exposedHeaders' => [], 'maxAge' => 0, 'hosts' => [], ], 'paths' => [ 'v1/*' => [ 'allowedOrigins' => ['*'], 'allowedHeaders' => ['*'], 'allowedMethods' => ['*'], 'maxAge' => 3600, ], ], ];

التوجيه ومعالجة طلبات HTTP

من أجل الإيجاز ، سأضع كل شفراتي داخل ملف path.php المسؤول عن توجيه Laravel وتفويض الطلبات إلى وحدات التحكم. عادةً ما ننشئ وحدات تحكم مخصصة للتعامل مع جميع طلبات HTTP الخاصة بنا والحفاظ على الكود الخاص بنا معياريًا ونظيفًا.

سنقوم بتحميل عرض AngularJS SPA الخاص بنا باستخدام

 Route::get('/', function () { return view('spa'); });

تسجيل المستخدم

عندما نقوم بتقديم طلب POST إلى /signup باستخدام اسم مستخدم وكلمة مرور ، سنحاول إنشاء مستخدم جديد وحفظه في قاعدة البيانات. بعد إنشاء المستخدم ، يتم إنشاء JWT وإعادته عبر استجابة JSON.

 Route::post('/signup', function () { $credentials = Input::only('email', 'password'); try { $user = User::create($credentials); } catch (Exception $e) { return Response::json(['error' => 'User already exists.'], HttpResponse::HTTP_CONFLICT); } $token = JWTAuth::fromUser($user); return Response::json(compact('token')); });

تسجيل دخول المستخدم

عندما نتقدم بطلب POST إلى /signin باستخدام اسم مستخدم وكلمة مرور ، نتحقق من وجود المستخدم ونعيد JWT عبر استجابة JSON.

 Route::post('/signin', function () { $credentials = Input::only('email', 'password'); if ( ! $token = JWTAuth::attempt($credentials)) { return Response::json(false, HttpResponse::HTTP_UNAUTHORIZED); } return Response::json(compact('token')); });

إحضار مصدر مقيد على نفس المجال

بمجرد تسجيل المستخدم الدخول ، يمكننا جلب المورد المقيد. لقد أنشأت مسارًا /restricted يحاكي موردًا يحتاج إلى مستخدم مصدق عليه. من أجل القيام بذلك ، يجب أن يوفر عنوان طلب Authorization أو سلسلة الاستعلام JWT للواجهة الخلفية للتحقق منها.

 Route::get('/restricted', [ 'before' => 'jwt-auth', function () { $token = JWTAuth::getToken(); $user = JWTAuth::toUser($token); return Response::json([ 'data' => [ 'email' => $user->email, 'registered_at' => $user->created_at->toDateTimeString() ] ]); } ]);

في هذا المثال ، أستخدم البرمجيات الوسيطة jwt-auth المتوفرة في الحزمة jwt-auth باستخدام 'before' => 'jwt-auth' . تُستخدم هذه البرامج الوسيطة لتصفية الطلب والتحقق من صحة رمز JWT. إذا كان الرمز المميز غير صالح ، أو غير موجود ، أو منتهي الصلاحية ، فإن البرنامج الوسيط سوف يطرح استثناء يمكننا اكتشافه.

في Laravel 5 ، يمكننا التقاط الاستثناءات باستخدام ملف app/Exceptions/Handler.php . باستخدام وظيفة render يمكننا إنشاء استجابات HTTP بناءً على الاستثناء الذي تم طرحه.

 public function render($request, Exception $e) { if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenInvalidException) { return response(['Token is invalid'], 401); } if ($e instanceof \Tymon\JWTAuth\Exceptions\TokenExpiredException) { return response(['Token has expired'], 401); } return parent::render($request, $e); }

إذا تمت مصادقة المستخدم وكان الرمز المميز صالحًا ، فيمكننا إعادة البيانات المقيدة بأمان إلى الواجهة الأمامية عبر JSON.

جلب الموارد المقيدة من النطاق الفرعي لواجهة برمجة التطبيقات

في مثال رمز الويب JSON التالي ، سنتخذ نهجًا مختلفًا للتحقق من صحة الرمز المميز. بدلاً من استخدام البرمجيات الوسيطة jwt-auth ، سنتعامل مع الاستثناءات يدويًا. عندما نقوم بتقديم طلب POST لخادم API api.jwt.dev/v1/restricted ، فإننا نقدم طلبًا عبر الأصل ، وعلينا تمكين CORS على الواجهة الخلفية. لحسن الحظ ، قمنا بالفعل بتكوين CORS في ملف config/cors.php .

 Route::group(['domain' => 'api.jwt.dev', 'prefix' => 'v1'], function () { Route::get('/restricted', function () { try { JWTAuth::parseToken()->toUser(); } catch (Exception $e) { return Response::json(['error' => $e->getMessage()], HttpResponse::HTTP_UNAUTHORIZED); } return ['data' => 'This has come from a dedicated API subdomain with restricted access.']; }); });

مثال على AngularJS Frontend

نحن نستخدم AngularJS كواجهة أمامية ، معتمدين على استدعاءات API لخادم مصادقة Laravel للجهة الخلفية لمصادقة المستخدم وعينة البيانات ، بالإضافة إلى خادم API لبيانات المثال عبر الأصل. بمجرد الانتقال إلى الصفحة الرئيسية لمشروعنا ، ستعمل الواجهة الخلفية على عرض resources/views/spa.blade.php التي ستعمل على تمهيد تطبيق Angular.

فيما يلي هيكل مجلد تطبيق Angular:

 public/ |-- css/ `-- bootstrap.superhero.min.css |-- lib/ |-- loading-bar.css |-- loading-bar.js `-- ngStorage.js |-- partials/ |-- home.html |-- restricted.html |-- signin.html `-- signup.html `-- scripts/ |-- app.js |-- controllers.js `-- services.js

تمهيد التطبيق الزاوي

يحتوي spa.blade.php على الأساسيات اللازمة لتشغيل التطبيق. سنستخدم Twitter Bootstrap للتصميم ، جنبًا إلى جنب مع سمة مخصصة من Bootswatch. للحصول على بعض الملاحظات المرئية عند إجراء مكالمة AJAX ، سنستخدم البرنامج النصي لشريط التحميل الزاوي ، والذي يعترض طلبات XHR وينشئ شريط تحميل. في قسم الرأس ، لدينا أوراق الأنماط التالية:

 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> <link rel="stylesheet" href="/css/bootstrap.superhero.min.css"> <link rel="stylesheet" href="/lib/loading-bar.css">

يحتوي تذييل العلامات الخاصة بنا على مراجع للمكتبات ، بالإضافة إلى البرامج النصية المخصصة الخاصة بنا للوحدات النمطية ووحدات التحكم والخدمات Angular.

 <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.14/angular-route.min.js"></script> <script src="/lib/ngStorage.js"></script> <script src="/lib/loading-bar.js"></script> <script src="/scripts/app.js"></script> <script src="/scripts/controllers.js"></script> <script src="/scripts/services.js"></script> </body>

نحن نستخدم مكتبة ngStorage لـ AngularJS ، لحفظ الرموز المميزة في التخزين المحلي للمتصفح ، حتى نتمكن من إرسالها في كل طلب عبر رأس Authorization .

في بيئة الإنتاج ، بالطبع ، سنقوم بتصغير ودمج جميع ملفات البرامج النصية وأوراق الأنماط من أجل تحسين الأداء.

لقد قمت بإنشاء شريط تنقل باستخدام Bootstrap والذي سيغير رؤية الروابط المناسبة ، اعتمادًا على حالة تسجيل دخول المستخدم. يتم تحديد حالة تسجيل الدخول من خلال وجود متغير token في نطاق وحدة التحكم.

 <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target=".navbar-collapse"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="#">JWT Angular example</a> </div> <div class="navbar-collapse collapse"> <ul class="nav navbar-nav navbar-right"> <li data-ng-show="token"><a ng-href="#/restricted">Restricted area</a></li> <li data-ng-hide="token"><a ng-href="#/signin">Sign in</a></li> <li data-ng-hide="token"><a ng-href="#/signup">Sign up</a></li> <li data-ng-show="token"><a ng-click="logout()">Logout</a></li> </ul> </div>

التوجيه

لدينا ملف يسمى app.js وهو مسؤول عن تكوين جميع مسارات الواجهة الأمامية.

 angular.module('app', [ 'ngStorage', 'ngRoute', 'angular-loading-bar' ]) .constant('urls', { BASE: 'http://jwt.dev:8000', BASE_API: 'http://api.jwt.dev:8000/v1' }) .config(['$routeProvider', '$httpProvider', function ($routeProvider, $httpProvider) { $routeProvider. when('/', { templateUrl: 'partials/home.html', controller: 'HomeController' }). when('/signin', { templateUrl: 'partials/signin.html', controller: 'HomeController' }). when('/signup', { templateUrl: 'partials/signup.html', controller: 'HomeController' }). when('/restricted', { templateUrl: 'partials/restricted.html', controller: 'RestrictedController' }). otherwise({ redirectTo: '/' });

هنا يمكننا أن نرى أننا حددنا أربعة مسارات يتم التعامل معها إما من خلال HomeController أو RestrictedController . كل مسار يتوافق مع عرض HTML الجزئي. لقد حددنا أيضًا ثابتين يحتويان على عناوين URL لطلبات HTTP الخاصة بنا إلى الخلفية.

طلب المعترض

تتيح لنا خدمة http $ الخاصة بـ AngularJS التواصل مع الواجهة الخلفية وتقديم طلبات HTTP. في حالتنا ، نريد اعتراض كل طلب HTTP وحقنه برأس Authorization يحتوي على JWT إذا تمت مصادقة المستخدم. يمكننا أيضًا استخدام المعترض لإنشاء معالج خطأ HTTP عالمي. فيما يلي مثال على جهاز اعتراض يقوم بحقن رمز مميز إذا كان متاحًا في التخزين المحلي للمتصفح.

 $httpProvider.interceptors.push(['$q', '$location', '$localStorage', function ($q, $location, $localStorage) { return { 'request': function (config) { config.headers = config.headers || {}; if ($localStorage.token) { config.headers.Authorization = 'Bearer ' + $localStorage.token; } return config; }, 'responseError': function (response) { if (response.status === 401 || response.status === 403) { $location.path('/signin'); } return $q.reject(response); } }; }]);

تحكم

في ملف controllers.js ، حددنا وحدتي تحكم لتطبيقنا: HomeController و RestrictedController . HomeController تسجيل الدخول والاشتراك وتسجيل الخروج. تمرر بيانات اسم المستخدم وكلمة المرور من نماذج تسجيل الدخول والاشتراك إلى خدمة Auth ، التي ترسل طلبات HTTP إلى الواجهة الخلفية. ثم يقوم بحفظ الرمز المميز في التخزين المحلي ، أو يعرض رسالة خطأ ، اعتمادًا على الاستجابة من الواجهة الخلفية.

 angular.module('app') .controller('HomeController', ['$rootScope', '$scope', '$location', '$localStorage', 'Auth', function ($rootScope, $scope, $location, $localStorage, Auth) { function successAuth(res) { $localStorage.token = res.token; window.location = "/"; } $scope.signin = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signin(formData, successAuth, function () { $rootScope.error = 'Invalid credentials.'; }) }; $scope.signup = function () { var formData = { email: $scope.email, password: $scope.password }; Auth.signup(formData, successAuth, function () { $rootScope.error = 'Failed to signup'; }) }; $scope.logout = function () { Auth.logout(function () { window.location = "/" }); }; $scope.token = $localStorage.token; $scope.tokenClaims = Auth.getTokenClaims(); }])

يتصرف RestrictedController بنفس الطريقة ، فقط يقوم بجلب البيانات باستخدام getRestrictedData و getApiData في خدمة Data .

 .controller('RestrictedController', ['$rootScope', '$scope', 'Data', function ($rootScope, $scope, Data) { Data.getRestrictedData(function (res) { $scope.data = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted content.'; }); Data.getApiData(function (res) { $scope.api = res.data; }, function () { $rootScope.error = 'Failed to fetch restricted API content.'; }); }]);

الواجهة الخلفية مسؤولة عن خدمة البيانات المقيدة فقط إذا تمت مصادقة المستخدم. هذا يعني أنه من أجل الاستجابة بالبيانات المقيدة ، يجب أن يحتوي طلب تلك البيانات على JWT صالح داخل عنوان Authorization أو سلسلة الاستعلام. إذا لم يكن الأمر كذلك ، فسوف يستجيب الخادم برمز حالة الخطأ 401 غير مصرح به.

خدمة المؤلف

خدمة المصادقة هي المسؤولة عن تسجيل الدخول وتسجيل طلبات HTTP في الخلفية. إذا كان الطلب ناجحًا ، فستحتوي الاستجابة على الرمز المميز الموقّع ، والذي يتم بعد ذلك فك تشفير base64 ، ويتم حفظ معلومات مطالبات الرمز المميز المرفقة في متغير tokenClaims . يتم تمرير هذا إلى وحدة التحكم عبر وظيفة getTokenClaims .

 angular.module('app') .factory('Auth', ['$http', '$localStorage', 'urls', function ($http, $localStorage, urls) { function urlBase64Decode(str) { var output = str.replace('-', '+').replace('_', '/'); switch (output.length % 4) { case 0: break; case 2: output += '=='; break; case 3: output += '='; break; default: throw 'Illegal base64url string!'; } return window.atob(output); } function getClaimsFromToken() { var token = $localStorage.token; var user = {}; if (typeof token !== 'undefined') { var encoded = token.split('.')[1]; user = JSON.parse(urlBase64Decode(encoded)); } return user; } var tokenClaims = getClaimsFromToken(); return { signup: function (data, success, error) { $http.post(urls.BASE + '/signup', data).success(success).error(error) }, signin: function (data, success, error) { $http.post(urls.BASE + '/signin', data).success(success).error(error) }, logout: function (success) { tokenClaims = {}; delete $localStorage.token; success(); }, getTokenClaims: function () { return tokenClaims; } }; } ]);

خدمة البيانات

هذه خدمة بسيطة تقدم طلبات إلى خادم المصادقة وكذلك خادم API لبعض البيانات المقيدة الوهمية. يقوم بإجراء الطلب ، ويفوض عمليات رد النداء الخاصة بالنجاح والخطأ إلى وحدة التحكم.

 angular.module('app') .factory('Data', ['$http', 'urls', function ($http, urls) { return { getRestrictedData: function (success, error) { $http.get(urls.BASE + '/restricted').success(success).error(error) }, getApiData: function (success, error) { $http.get(urls.BASE_API + '/restricted').success(success).error(error) } }; } ]);

ما وراء برنامج JSON Web Token التعليمي

تتيح لنا المصادقة القائمة على الرمز المميز إنشاء أنظمة منفصلة غير مرتبطة بنظام مصادقة معين. قد يتم إنشاء الرمز المميز في أي مكان واستهلاكه على أي نظام يستخدم نفس المفتاح السري لتوقيع الرمز المميز. إنها جاهزة للجوال ولا تتطلب منا استخدام ملفات تعريف الارتباط.

تعمل رموز الويب JSON عبر جميع لغات البرمجة الشائعة وتكتسب شعبية بسرعة. وهي مدعومة من قبل شركات مثل Google و Microsoft و Zendesk. لا تزال المواصفات القياسية الخاصة بهم من قبل فريق عمل هندسة الإنترنت (IETF) في نسخة المسودة وقد تتغير قليلاً في المستقبل.

لا يزال هناك الكثير لتغطيته حول JWTs ، مثل كيفية التعامل مع تفاصيل الأمان وتحديث الرموز المميزة عند انتهاء صلاحيتها ، ولكن يجب أن يوضح البرنامج التعليمي JSON Web Token الاستخدام الأساسي ، والأهم من ذلك ، مزايا استخدام JWTs.

مزيد من القراءة على مدونة Toptal Engineering:

  • بناء Node.js / TypeScript REST API ، الجزء 3: MongoDB والمصادقة والاختبارات الآلية