تنفيذ تقنية SSO على أساس Node.js

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







إذا كنا نتحدث عن مشروع ويب واحد ، فمن السهل الحفاظ على المعلومات المتعلقة بحالة جلسة معينة من التفاعل بين العميل والخادم باستخدام مصادقة المستخدم عند تسجيل دخوله. ولكن إذا تطور مثل هذا النظام المستقل ، وتحول إلى عدة أنظمة ، فإن المطور يواجه مسألة الحفاظ على المعلومات حول حالة كل من هذه الأنظمة المنفصلة. من الناحية العملية ، يبدو هذا السؤال على النحو التالي: "هل يتعين على مستخدم هذه الأنظمة إدخال كل منها على حدة والخروج منها أيضًا؟"



هناك قاعدة عامة جيدة حول الأنظمة التي يزداد تعقيدها بمرور الوقت وكيف تتفاعل هذه الأنظمة مع مستخدميها. وبالتحديد ، يقع عبء حل المشكلات المرتبطة بتعقيد بنية المشروع على عاتق النظام وليس على مستخدميه. لا يهم مدى تعقيد الآليات الداخلية لمشروع الويب. يجب أن يبدو مثل نظام موحد للمستخدم. بمعنى آخر ، يجب على المستخدم الذي يعمل بنظام ويب يتكون من العديد من المكونات أن يدرك ما يحدث كما لو كان يعمل بنظام واحد. على وجه الخصوص ، نحن نتحدث عن المصادقة في مثل هذه الأنظمة باستخدام SSO (الدخول الموحد) - وهي تقنية تسجيل دخول واحد.



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



في بيئة اليوم ، يتم إعاقة مثل هذه الحلول من خلال التبني الواسع لبنى الخدمات المصغرة. أصبحت إدارة الجلسات أكثر تعقيدًا في الوقت الذي تم فيه استخدام تقنيات مختلفة في تطوير مشاريع الويب ، وعندما يتم استضافة خدمات مختلفة في بعض الأحيان على مجالات مختلفة. بالإضافة إلى ذلك ، بدأت خدمات الويب التي كانت تُكتب بلغة Java في الكتابة باستخدام إمكانيات النظام الأساسي Node.js. هذا جعل من الصعب العمل مع ملفات تعريف الارتباط. اتضح أن الجلسات الآن ليست سهلة الإدارة.



أدت هذه الصعوبات إلى تطوير طرق جديدة لتسجيل الدخول إلى الأنظمة ، على وجه الخصوص ، نحن نتحدث عن تقنية الدخول الأحادي.



تقنية تسجيل الدخول الأحادي



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



نحن ، لأغراض تعليمية ، بصدد تنفيذ تقنية SSO على منصة Node.js.



وتجدر الإشارة إلى أن تطبيق هذه التقنية على مستوى الشركة يتطلب جهدًا أكبر بكثير مما سنضعه في تطوير نظام التدريب لدينا. هذا هو السبب في وجود حلول SSO متخصصة مصممة للمشاريع واسعة النطاق.



كيف يتم تنظيم تسجيل الدخول الموحد؟



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



ها هو مستودع الكود الخاص بمشروع simple-sso ، والذي سأصفه هنا. أنا أستخدم إطار عمل Node.js ، لكن يمكنك تطبيقه باستخدام شيء مختلف. لنقم بتحليل خطوة بخطوة لإجراءات المستخدم الذي يعمل مع النظام والآليات التي يتكون منها هذا النظام.



الخطوة 1



يحاول المستخدم الوصول إلى مورد محمي على النظام (دعنا نسمي هذا المورد "مستهلك SSO" ، "sso-Consumer"). يكتشف مستهلك SSO أن المستخدم لم يسجل الدخول ويعيد توجيه المستخدم إلى "خادم SSO" ("sso-server") باستخدام عنوانه كمعامل استعلام. سيتم إعادة توجيه المستخدم المصادق عليه بنجاح إلى هذا العنوان. يتم توفير هذه الآلية بواسطة برنامج وسيط Express:



const isAuthenticated = (req, res, next) => {
  //   ,   ,
  //     -     SSO-     
  //    URL  URL,     
  // ,   
  const redirectURL = `${req.protocol}://${req.headers.host}${req.path}`;
  if (req.session.user == null) {
    return res.redirect(
      `http://sso.ankuranand.com:3010/simplesso/login?serviceURL=${redirectURL}`
    );
  }
  next();
};

module.exports = isAuthenticated;


الخطوة 2



اكتشف خادم SSO أن المستخدم لم يسجل الدخول ويعيد توجيهه إلى صفحة تسجيل الدخول:



const login = (req, res, next) => {
  //  req.query  url,      
  //    ,     sso-.
  //        
  //     
  const { serviceURL } = req.query;
  //         URL.
  if (serviceURL != null) {
    const url = new URL(serviceURL);
    if (alloweOrigin[url.origin] !== true) {
      return res
        .status(400)
        .json({ message: "Your are not allowed to access the sso-server" });
    }
  }
  if (req.session.user != null && serviceURL == null) {
    return res.redirect("/");
  }
  //          -  
  //   
  if (req.session.user != null && serviceURL != null) {
    const url = new URL(serviceURL);
    const intrmid = encodedId();
    storeApplicationInCache(url.origin, req.session.user, intrmid);
    return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
  }

  return res.render("login", {
    title: "SSO-Server | Login"
  });
};


سأدلي ببعض التعليقات هنا بخصوص الأمن.



نتحقق من serviceURLمعلمة الطلب الوارد إلى خادم SSO. يتيح لنا ذلك معرفة ما إذا كان عنوان URL هذا مسجلاً في النظام وما إذا كانت الخدمة التي يمثلها يمكنها استخدام خدمات خادم SSO.



إليك ما قد تبدو عليه قائمة عناوين URL للخدمات المسموح لها باستخدام خادم SSO:



const alloweOrigin = {
"http://consumer.ankuranand.in:3020": true,
"http://consumertwo.ankuranand.in:3030": true,
"http://test.tangledvibes.com:3080": true,
"http://blog.tangledvibes.com:3080": fasle,
};


الخطوه 3



يقوم المستخدم بإدخال اسم مستخدم وكلمة مرور يتم إرسالها إلى خادم الدخول الموحد في طلب تسجيل الدخول.





صفحة تسجيل الدخول



الخطوة 4



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



الخطوة الخامسة



يأخذ خادم الدخول الموحّد رمز التفويض ويمرره إلى المكان الذي جاء منه المستخدم الذي سجّل الدخول حديثًا (أي أنه يمرر الرمز المميز إلى مستهلك SSO).



const doLogin = (req, res, next) => {
  //         .
  //         , 
  // userDB -   ,   ,   
  const { email, password } = req.body;
  if (!(userDB[email] && password === userDB[email].password)) {
    return res.status(404).json({ message: "Invalid email and password" });
  }

  //     
  const { serviceURL } = req.query;
  const id = encodedId();
  req.session.user = id;
  sessionUser[id] = email;
  if (serviceURL == null) {
    return res.redirect("/");
  }
  const url = new URL(serviceURL);
  const intrmid = encodedId();
  storeApplicationInCache(url.origin, id, intrmid);
  return res.redirect(`${serviceURL}?ssoToken=${intrmid}`);
};


مرة أخرى ، بعض الملاحظات الأمنية:



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


الخطوة 6



يتلقى مستهلك SSO رمزًا مميزًا ويتصل بخادم SSO للتحقق من الرمز المميز. يتحقق الخادم من الرمز المميز ويعيد رمزًا مميزًا آخر بمعلومات المستخدم. يستخدم مستهلك SSO هذا الرمز المميز لإنشاء جلسة مع المستخدم. هذه الجلسة تسمى المحلية.



إليك رمز البرنامج الوسيط المستخدم في مستهلك SSO المعتمد على Express:



const ssoRedirect = () => {
  return async function(req, res, next) {
    // ,    req queryParameter,  ssoToken,
    //  ,    .
    const { ssoToken } = req.query;
    if (ssoToken != null) {
      //   ssoToken   ,  .
      const redirectURL = url.parse(req.url).pathname;
      try {
        const response = await axios.get(
          `${ssoServerJWTURL}?ssoToken=${ssoToken}`,
          {
            headers: {
              Authorization: "Bearer l1Q7zkOL59cRqWBkQ12ZiGVW2DBL"
            }
          }
        );
        const { token } = response.data;
        const decoded = await verifyJwtToken(token);
        //      jwt,  
        // global-session-id  id ,  
        //         .
        req.session.user = decoded;
      } catch (err) {
        return next(err);
      }

      return res.redirect(`${redirectURL}`);
    }

    return next();
  };
};


بعد تلقي طلب من مستهلك SSO ، يتحقق الخادم من الرمز المميز من وجوده وتاريخ انتهاء صلاحيته. يعتبر رمز التحقق من صحته صالحًا.



في حالتنا ، يقوم خادم SSO ، بعد التحقق الناجح من الرمز المميز ، بإرجاع JWT موقع به معلومات حول المستخدم.



const verifySsoToken = async (req, res, next) => {
  const appToken = appTokenFromRequest(req);
  const { ssoToken } = req.query;
  //        ssoToken .
  //  ssoToken    - ,   .
  if (
    appToken == null ||
    ssoToken == null ||
    intrmTokenCache[ssoToken] == null
  ) {
    return res.status(400).json({ message: "badRequest" });
  }

  //  appToken  -     
  const appName = intrmTokenCache[ssoToken][1];
  const globalSessionToken = intrmTokenCache[ssoToken][0];
  //  appToken   ,   SSO-        
  if (
    appToken !== appTokenDB[appName] ||
    sessionApp[globalSessionToken][appName] !== true
  ) {
    return res.status(403).json({ message: "Unauthorized" });
  }
  // ,     
  const payload = generatePayload(ssoToken);

  const token = await genJwtToken(payload);
  //    ,     
  delete intrmTokenCache[ssoToken];
  return res.status(200).json({ token });
};


إليك بعض ملاحظات الأمان.



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


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



const userDB = {
  "info@ankuranand.com": {
    password: "test",
    userId: encodedId(), //   ,         .
    appPolicy: {
      sso_consumer: { role: "admin", shareEmail: true },
      simple_sso_consumer: { role: "user", shareEmail: false }
    }
  }
};


بعد أن ينجح المستخدم في الدخول إلى النظام ، يتم إنشاء جلسات بينه وبين خادم SSO ، وكذلك بينه وبين كل نظام فرعي. تسمى الجلسة التي تم إنشاؤها بين المستخدم وخادم الدخول الموحد بجلسة عامة. تسمى الجلسة التي يتم إنشاؤها بين مستخدم ونظام فرعي يوفر للمستخدم بعض الخدمات جلسة محلية. بعد إنشاء الجلسة المحلية ، سيتمكن المستخدم من العمل مع موارد النظام الفرعي المغلقة أمام موارد خارجية.





إقامة جلسات محلية وعالمية



جولة سريعة في مستهلك SSO وخادم SSO



لنأخذ جولة سريعة في مستهلك SSO ووظائف خادم SSO.



▍ مستهلك SSO



  1. لا يقوم النظام الفرعي للمستهلك SSO بمصادقة المستخدم من خلال إعادة توجيه المستخدم إلى خادم SSO.
  2. يتلقى هذا النظام الفرعي الرمز المميز الذي تم تمريره إليه بواسطة خادم الدخول الموحد.
  3. يتفاعل مع الخادم ، ويتحقق من صلاحية الرمز المميز.
  4. تتلقى JWT وتتحقق من صحة هذا الرمز باستخدام المفتاح العام.
  5. هذا النظام الفرعي يؤسس جلسة محلية.


خادم ▍SSO



  1. يتحقق خادم SSO من صحة معلومات تسجيل دخول المستخدم.
  2. يقوم الخادم بإنشاء جلسة عامة.
  3. يقوم بإنشاء رمز ترخيص مميز.
  4. يتم إرسال رمز ترخيص مميز إلى مستهلك SSO.
  5. يتحقق الخادم من صحة الرموز المميزة التي تم تمريرها إليه من قبل مستهلكي SSO.
  6. يرسل الخادم SSO JWT إلى المستهلك بمعلومات المستخدم.


تنظيم الخروج المركزي



على غرار الطريقة التي تم بها تطبيق SSO ، يمكنك تنفيذ تقنية SSO. هنا تحتاج فقط إلى مراعاة الاعتبارات التالية:



  1. في حالة وجود جلسة محلية ، يجب أن توجد جلسة عامة أيضًا.
  2. في حالة وجود جلسة عامة ، فهذا لا يعني بالضرورة وجود جلسة محلية.
  3. إذا تم إتلاف جلسة العمل المحلية ، يجب أيضًا تدمير الجلسة العمومية.


النتيجة



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



هل تستخدم مشاريعك آليات تسجيل الدخول الموحد؟






All Articles