JWT وOAuth2 وتأمين الواجهات

مقدمة إلى OAuth2 و OpenID Connect

18 دقيقة الدرس 8 من 13

مقدمة إلى OAuth2 و OpenID Connect

مهارات JWT التي بنيتها حتى الآن قوية — لكنها تغطّي نصفًا واحدًا فقط من بنية الأمان الحديثة: المصادقة المخصصة التي تتحكم بها من أوّلها إلى آخرها. في الواقع العملي، كثيرًا ما تحتاج خدمتك إلى السماح للمستخدمين بتسجيل الدخول عبر Google أو GitHub، أو منح تطبيق جوّال وصولًا محدودًا لواجهتك البرمجية دون الكشف عن كلمة المرور. هذه بالضبط هي المشكلة التي صُمِّم OAuth 2.0 و OpenID Connect (OIDC) لحلّها.

لماذا وُجد OAuth2 — مشكلة التفويض

تخيّل أن مستخدمًا يريد منح خدمة طباعة صور وصولًا إلى صور Google الخاصة به دون أن يسلّمها كلمة مروره على Google. قبل OAuth، كان الحل الشائع هو إعطاء التطبيق الخارجي بيانات اعتماد المستخدم — ممارسةٌ خطيرة ولا يمكن إلغاؤها بصورة انتقائية.

يُقدّم OAuth 2.0 (RFC 6749، 2012) إطار عمل التفويض المُفوَّض. يُفوّض المستخدم تطبيقًا خارجيًا للتصرف نيابةً عنه دون أن يعرف التطبيق كلمة مروره. بدلًا من ذلك يتلقى رمز وصول access token محدود النطاق ومحدود الزمن. يستطيع المستخدم إلغاء هذا الرمز في أي وقت دون تغيير كلمة مروره.

OAuth2 إطار تفويض وليس بروتوكول مصادقة. يجيب عن السؤال: "ما الذي يحق لهذا التطبيق فعله؟" — لا عن: "من هذا المستخدم؟" يُضيف OpenID Connect طبقة الهوية فوق ذلك.

الأدوار الأربعة الأساسية

  • مالك المورد (Resource Owner) — المستخدم النهائي (أو النظام) الذي يملك البيانات المحمية.
  • العميل (Client) — التطبيق الطالب للوصول (مثل خدمة Spring Boot الخاصة بك، تطبيق جوّال، أو SPA).
  • خادم التفويض (Authorization Server) — الخادم الذي يُصادق مالك المورد ويُصدر الرموز (مثل Keycloak، Auth0، Okta، Google).
  • خادم المورد (Resource Server) — الـ API الذي يحتوي على الموارد المحمية ويتحقق من الرموز قبل تقديمها. في الدرس التالي يكون تطبيق Spring Boot الخاص بك هو خادم المورد.

أنواع التفويض (Grant Types) — كيف يحصل العميل على رمز

يُعرِّف نوع التفويض (grant type) تدفق البروتوكول الذي يستخدمه العميل للحصول على رمز وصول. يُعرِّف OAuth 2.0 عدة أنواع؛ عليك معرفة أربعة منها.

1. تفويض كود التفويض (Authorization Code Grant) + PKCE

هذا هو أأمن الأنواع وأكثرها استخدامًا للتطبيقات التي تمتلك مكوّنًا من جانب الخادم أو واجهة أمامية تعمل في المتصفح. التدفق:

  1. يُعيد توجيه العميل متصفح المستخدم إلى خادم التفويض مع إرسال response_type=code و client_id و redirect_uri و scope.
  2. يُصادق المستخدم ويوافق على المنح في خادم التفويض.
  3. يُعيد خادم التفويض توجيه المتصفح إلى redirect_uri مع إرفاق كود تفويض قصير الأمد.
  4. يُرسل العميل (من جانب الخادم) الكود للحصول على الرموز عبر طلب POST لنقطة نهاية الرمز — يتضمّن هذا الطلب client_secret الذي لا يغادر الخادم أبدًا.
  5. يُعيد خادم التفويض access_token، واختياريًا refresh_token، و (إذا كان OIDC) id_token.

صُمِّم PKCE (Proof Key for Code Exchange) في الأصل لتطبيقات الجوال التي لا تستطيع حماية سر العميل، لكن RFC 9700 يُلزم باستخدامه مع جميع العملاء العامين (SPAs، الجوال). يُنشئ العميل code_verifier عشوائيًا، ويُجزّئه للحصول على code_challenge، ويُرسل التحدي في الخطوة 1. في الخطوة 4 يُرسل المُتحقق الأصلي ويتحقق خادم التفويض منه. هذا يمنع المهاجم الذي يعترض الكود من استبداله.

# الخطوة 1: إعادة توجيه المتصفح إلى: GET https://auth.example.com/authorize ?response_type=code &client_id=my-app &redirect_uri=https://app.example.com/callback &scope=openid%20profile%20email%20api:read &state=random-csrf-token &code_challenge=BASE64URL(SHA256(code_verifier)) &code_challenge_method=S256 # الخطوة 4: تبادل الرمز بين خادم وخادم POST https://auth.example.com/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=AUTH_CODE_FROM_STEP_3 &redirect_uri=https://app.example.com/callback &client_id=my-app &client_secret=SECRET (للعملاء السريين فقط) &code_verifier=ORIGINAL_VERIFIER
تحقق دائمًا من معامل state عند الاستدعاء. إنه رمز CSRF — بدون التحقق منه يستطيع المهاجم تنفيذ هجوم CSRF للتسجيل وربط حسابه بجلسة الضحية.

2. تفويض بيانات اعتماد العميل (Client Credentials Grant)

يُستخدم في اتصالات الآلة بالآلة (M2M) — خدمة مصغرة تستدعي خدمة أخرى دون وجود مستخدم بشري. يُصادق العميل مباشرةً ببيانات اعتماده الخاصة؛ لا يوجد إعادة توجيه للمتصفح ولا موافقة من مالك المورد.

POST https://auth.example.com/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &client_id=order-service &client_secret=ORDER_SERVICE_SECRET &scope=inventory:read inventory:write

الاستجابة هي رمز وصول محدود النطاق بما يحق للـ خدمة فعله. هذا هو نوع التفويض الذي ستُهيّئه في Spring Security حين تحتاج خدمتك المصغرة إلى استدعاء API خارجي.

3. تفويض رمز التحديث (Refresh Token Grant)

رموز الوصول قصيرة الأمد عمدًا (عادةً 5–15 دقيقة). عند انتهاء صلاحية رمز الوصول، يستطيع العميل استخدام رمز التحديث — الذي صدر مع رمز الوصول ويمتلك عمرًا أطول — للحصول بصمت على رمز وصول جديد دون إزعاج المستخدم.

POST https://auth.example.com/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &refresh_token=REFRESH_TOKEN_VALUE &client_id=my-app &client_secret=SECRET
رموز التحديث أسرار عالية القيمة. خزّنها مشفّرةً أثناء التخزين (جلسة من جانب الخادم أو ملف تعريف ارتباط مشفّر). إذا سُرق رمز التحديث استطاع المهاجم سكّ رموز وصول جديدة إلى أجل غير مسمى حتى يُلغيه خادم التفويض. استخدم دائمًا تدوير رمز التحديث — كل استخدام للرمز يُبطل القديم ويُصدر جديدًا، فيتم اكتشاف الرمز المسروق المُعاد تشغيله.

4. التفويض الضمني (Implicit Grant) — مُهمَل

كان التفويض الضمني يُعيد رمز الوصول مباشرةً من نقطة نهاية التفويض — دون تبادل كود ودون سر عميل. صُمِّم لتطبيقات SPA في المتصفح قبل ظهور PKCE لكنه يعاني من ثغرات أمنية جوهرية (الرموز مرئية في جزء URL، وغياب ربط المُرسل). لا تستخدمه في التطبيقات الجديدة. استبدله بـ Authorization Code + PKCE.

OpenID Connect (OIDC) — إضافة الهوية

يخبر OAuth 2.0 خادم المورد بما يحق للعميل فعله. لكنه لا يخبر تطبيقك من هو المستخدم. OpenID Connect طبقة هوية رفيعة مبنية فوق OAuth 2.0 تُضيف بالضبط ذلك.

عندما يطلب العميل نطاق openid، يُضمِّن خادم التفويض رمز الهوية (ID Token) في استجابة الرمز إلى جانب رمز الوصول. رمز الهوية هو JWT موقَّع يحتوي على:

  • iss — معرف المُصدِر (خادم التفويض).
  • sub — معرف الموضوع (المعرف الفريد للمستخدم لدى المُصدِر).
  • aud — الجمهور المقصود (معرف عميلك).
  • exp و iat — طوابع انتهاء الصلاحية والإصدار.
  • nonce — قيمة أرسلتها في طلب التفويض لمنع هجمات إعادة التشغيل.
  • مطالبات الملف الشخصي المعيارية: name، email، picture، locale، وغيرها (مقيّدة بنطاقات مثل profile و email).
// مثال على حمولة OIDC ID Token مُفكَّكة { "iss": "https://accounts.google.com", "sub": "110248495819764", "aud": "812741506391.apps.googleusercontent.com", "exp": 1718900000, "iat": 1718896400, "nonce": "0394852-3190485-2490358", "email": "alice@example.com", "email_verified": true, "name": "Alice Example", "picture": "https://lh3.googleusercontent.com/..." }

يجب على العميل التحقق من رمز الهوية قبل الثقة بأي مطالبة: التحقق من التوقيع عبر نقطة نهاية JWKS الخاصة بالمُصدِر، والتأكد من iss و aud و exp و nonce. يقوم Spring Security OAuth2 Login بكل ذلك تلقائيًا.

النطاقات (Scopes) ونقطة نهاية معلومات المستخدم (UserInfo)

تُحدد نطاقات OAuth2 ما يُسمح لرمز الوصول بفعله. يُعرِّف OIDC نطاقات معيارية:

  • openid — مطلوب لتفعيل OIDC؛ يُعيد رمز الهوية.
  • profile — name، family_name، given_name، picture، locale.
  • email — email و email_verified.
  • address و phone — العنوان البريدي ورقم الهاتف.
  • offline_access — يطلب رمز تحديث.

المطالبات الإضافية غير الموجودة في رمز الهوية يمكن جلبها من نقطة نهاية UserInfo — نقطة نهاية REST محمية على خادم التفويض تقبل رمز الوصول وتُعيد خصائص المستخدم. يستدعيها Spring Security تلقائيًا عند تهيئة OAuth2 Login.

وثيقة الاكتشاف (Discovery Document)

ينشر أي خادم تفويض متوافق مع OIDC وثيقة الاكتشاف على /.well-known/openid-configuration. تسرد جميع عناوين نقاط النهاية والنطاقات المدعومة وخوارزميات التوقيع ورابط JWKS. يقرأ Spring Security هذا تلقائيًا عند تعيين spring.security.oauth2.resourceserver.jwt.issuer-uri — لا حاجة لتهيئة نقاط النهاية يدويًا.

# application.yml — الحد الأدنى من إعداد خادم المورد spring: security: oauth2: resourceserver: jwt: issuer-uri: https://accounts.google.com # يجلب Spring مستند /.well-known/openid-configuration ويتحقق من الرموز # باستخدام JWKS المنشور تلقائيًا

OAuth2 مقابل إعداد JWT المخصص الخاص بك

رموز JWT التي أنشأتها في الدروس 3–5 هي رموز مملوكة لك — تطبيقك هو المُصدِر والمُتحقق في آنٍ واحد. يعمل ذلك جيدًا للأنظمة البسيطة. يُناسب OAuth2/OIDC الحالات التالية:

  • تريد أن يُسجّل المستخدمون الدخول عبر مزوّد هوية خارجي (Google، GitHub، IdP مؤسسي).
  • تشغّل خدمات متعددة وتريد خادم تفويض واحدًا يُصدر رموزًا تثق بها جميع الخدمات.
  • تحتاج إلى وصول API مُفوَّض — السماح لتطبيق خارجي باستدعاء API الخاص بك نيابةً عن مستخدم.
  • تتطلب الامتثال هوية قائمة على معايير (FIDO2، SSO مؤسسي، مسارات تدقيق).

الخلاصة

يُعرِّف OAuth 2.0 أربعة أدوار (مالك المورد، العميل، خادم التفويض، خادم المورد) وأنواع تفويض متعددة لكل سيناريو: Authorization Code + PKCE للتطبيقات الموجهة للمستخدمين، Client Credentials للاتصالات بين الآلات، وRefresh Token للتجديد الصامت. تُضيف OpenID Connect طبقة الهوية فوق ذلك عبر رمز الهوية — JWT موقَّع يحمل مطالبات هوية المستخدم. في الدرس التالي ستُهيّئ تطبيق Spring Boot الخاص بك كـ OAuth2 Resource Server يتحقق من رموز JWT الصادرة عن خادم تفويض خارجي.

ES
Edrees Salih
منذ ساعة

We are still cooking the magic in the way!