البرنامج التعليمي - إضافة المصادقة والأذونات إلى التطبيق الخاص بك عند استخدام Azure Web PubSub

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

في تطبيق حقيقي، عادة ما ترغب من المستخدم تسجيل الدخول أولًا، قبل أن يتمكنوا من استخدام التطبيق الخاص بك. في هذا البرنامج التعليمي، تتعلم كيفية دمج Web PubSub مع نظام المصادقة والتخويل للتطبيق الخاص بك، لجعله أكثر أمنًا.

يمكنك العثور على التعليمة البرمجية الكاملة لهذا البرنامج التعليمي على GitHub.

في هذا البرنامج التعليمي، تتعلم كيفية:

  • تمكين مصادقة GitHub
  • إضافة برنامج المصادقة الوسيط إلى تطبيقك
  • إضافة الأذونات إلى العملاء

إضافة المصادقة إلى تطبيق غرفة الدردشة

يعيد هذا البرنامج التعليمي استخدام تطبيق الدردشة الذي أُنشئ في إنشاء تطبيقات دردشة. يمكنك أيضًا استنساخ نموذج التعليمة البرمجية الكامل لتطبيق الدردشة من GitHub.

في هذا البرنامج التعليمي، يمكنك إضافة المصادقة إلى تطبيق الدردشة ودمجها مع Web PubSub.

أولًا، أضف مصادقة GitHub إلى غرفة المحادثة حتى يتمكن المستخدم من استخدام حساب GitHub لتسجيل الدخول.

  1. تثبيت التبعيات:

    npm install --save cookie-parser
    npm install --save express-session
    npm install --save passport
    npm install --save passport-github2
    
  2. ابحث عن server.js الملف في دليلك وقم بتمكين مصادقة GitHub عن طريق إضافة التعليمات البرمجية التالية إلى server.js:

    const app = express();
    
    const users = {};
    passport.use(
      new GitHubStrategy({
        clientID: process.argv[3],
        clientSecret: process.argv[4]
      },
      (accessToken, refreshToken, profile, done) => {
        users[profile.id] = profile;
        return done(null, profile);
      }
    ));
    
    passport.serializeUser((user, done) => {
      done(null, user.id);
    });
    
    passport.deserializeUser((id, done) => {
      if (users[id]) return done(null, users[id]);
      return done(`invalid user id: ${id}`);
    });
    
    app.use(cookieParser());
    app.use(session({
      resave: false,
      saveUninitialized: true,
      secret: 'keyboard cat'
    }));
    app.use(passport.initialize());
    app.use(passport.session());
    app.get('/auth/github', passport.authenticate('github', { scope: ['user:email'] }));
    app.get('/auth/github/callback', passport.authenticate('github', { successRedirect: '/' }));
    

    تستخدم التعليمة البرمجية المذكورة أعلاه Passport.js لتمكين مصادقة GitHub. وفيما يلي توضيح بسيط لكيفية عملها:

    1. /auth/githubإعادة توجيه إلى github.com لتسجيل الدخول.
    2. بعد تسجيل الدخول، تقوم GitHub بإعادة توجيهك إلى /auth/github/callback مع رمز للتطبيق الخاص بك لإكمال المصادقة. (لمعرفة كيفية التحقق من ملف التعريف الذي تم إرجاعه من GitHub واستمراره في الخادم، راجع رد الاتصال الذي تم التحقق منه في passport.use().)
    3. وبعد اكتمال المصادقة، يُعاد توجيهك إلى الصفحة الرئيسية لموقع (/).

    للحصول على مزيد من التفاصيل حول GitHub OAuth وPassport.js، يُرجى الاطلاع على المقالات التالية:

    إذا أردت اختبار ذلك، فلا بد لك من إنشاء تطبيق OAuth GitHub أولاً:

    1. انتقل إلى https://www.github.com، وافتح "Your Profile"، ثم حدد Settings>Developer settings.
    2. انتقل إلى "OAuth Apps"، ثم حدد New OAuth App.
    3. املأ اسم التطبيق وعنوان URL للصفحة الرئيسية (يمكن أن يكون عنوان URL أي شيء يعجبك)، ثم قم بتعيين عنوان URL لرد اتصال التصديق إلى http://localhost:8080/auth/github/callback. يطابق عنوان URL هذا واجهة برمجة تطبيقات رد الاتصال التي قمت بكشفها في الخادم.
    4. بعد تسجيل التطبيق، يمكنك نسخ مُعرف العميل وتحديد إنشاء سر عميل جديد.

    قم بتشغيل الأمر أدناه لاختبار الإعدادات، ولا تنسَ استبدال <connection-string>،<client-id>، و <client-secret> بقيمك.

    export WebPubSubConnectionString="<connection-string>"
    export GitHubClientId="<client-id>"
    export GitHubClientSecret="<client-secret>"
    node server
    

    الآن فتح http://localhost:8080/auth/github. تتم إعادة توجيهك إلى GitHub لتسجيل الدخول. بعد تسجيل الدخول، تتم إعادة توجيهك إلى تطبيق الدردشة.

  3. قم بتحديث غرفة الدردشة للاستفادة من الهوية التي تحصل عليها من GitHub، بدلًا من مطالبة المستخدم باسم مستخدم.

    تحديث public/index.html لاستدعاء /negotiate مباشرة دون تمرير مُعرف المستخدم.

    let messages = document.querySelector('#messages');
    let res = await fetch(`/negotiate`);
    if (res.status === 401) {
      let m = document.createElement('p');
      m.innerHTML = 'Not authorized, click <a href="/auth/github">here</a> to login';
      messages.append(m);
      return;
    }
    let data = await res.json();
    let ws = new WebSocket(data.url);
    

    عند تسجيل دخول مستخدم، يقوم الطلب تلقائيًّا بتحميل هوية المستخدم من خلال ملف تعريف ارتباط. لذا تحتاج فقط للتحقق مما إذا كان المستخدم موجودًا في عنصر req، وإضافة اسم المستخدم إلى الرمز المميز للوصول لـ Web PubSub:

    app.get('/negotiate', async (req, res) => {
      if (!req.user || !req.user.username) {
        res.status(401).send('missing user id');
        return;
      }
      let options = {
        userId: req.user.username
      };
      let token = await serviceClient.getClientAccessToken(options);
      res.json({
        url: token.url
      });
    });
    

    الآن أعد تشغيل الخادم، وترى رسالة "غير مصرح بها" للمرة الأولى التي تفتح فيها غرفة المحادثة. حدد ارتباط تسجيل الدخول لتسجيل الدخول، ثم ترى أنه يعمل كما كان من قبل.

العمل باستخدام الأذونات

في البرامج التعليمية السابقة، تعلمت استخدام WebSocket.send() لنشر الرسائل مباشرة إلى العملاء الآخرين عَبر البروتوكول الفرعي. في تطبيق حقيقي، قد لا ترغب من العميل أن يكون قادرًا على نشر أو الاشتراك في أي مجموعة بدون إذن التحكم. في هذا القسم، سترى كيفية التحكم في العملاء باستخدام نظام الأذونات الخاص ب Web PubSub.

في Web PubSub، يمكن للعميل تنفيذ الأنواع التالية من العمليات مع البروتوكول الفرعي:

  • إرسال الأحداث إلى الخادم.
  • نشر الرسائل إلى إحدى المجموعات.
  • الانضمام إلى مجموعة (الاشتراك فيها).

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

  • تحديد الأدوار عند اتصال العميل (الدور هو مفهوم لتمثيل الأذونات الأولية عند اتصال أحد العملاء).
  • استخدم واجهة برمجة تطبيقات لمنح الإذن لعميل بعد اتصاله.

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

والآن لنستخدم نظام الأذونات هذا لإضافة ميزة جديدة إلى غرفة الدردشة. يمكنك إضافة نوع جديد من المستخدمين يسمى المسؤول إلى غرفة المحادثة. تسمح للمسؤول بإرسال رسائل النظام (الرسائل التي تبدأ ب "[SYSTEM]") مباشرة من العميل.

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

تغيير server.js لإرسال رسائل متنوعة إلى مختلف المجموعات:

let handler = new WebPubSubEventHandler(hubName, {
  path: '/eventhandler',
  handleConnect: (req, res) => {
    res.success({
      groups: ['system', 'message'],
    });
  },
  onConnected: req => {
    console.log(`${req.context.userId} connected`);
    serviceClient.group('system').sendToAll(`${req.context.userId} joined`, { contentType: 'text/plain' });
  },
  handleUserEvent: (req, res) => {
    if (req.context.eventName === 'message') {
      serviceClient.group('message').sendToAll({
        user: req.context.userId,
        message: req.data
      });
    }
    res.success();
  }
});

تستخدم التعليمات البرمجية السابقة WebPubSubServiceClient.group().sendToAll() لإرسال الرسالة إلى مجموعة بدلًا من المركز.

نظرًا لأن الرسالة يتم إرسالها الآن إلى مجموعات، تحتاج إلى إضافة عملاء إلى مجموعات حتى يتمكنوا من متابعة تلقي الرسائل. استخدم handleConnect المعالج لإضافة عملاء إلى مجموعات.

إشعار

تُشغل handleConnect بمجرد أن يحاول أحد العملاء الاتصال بخدمة Web PubSub. في هذا المعالج، يمكنك إرجاع المجموعات والأدوار، بحيث يمكن للخدمة إضافة اتصال إلى المجموعات أو منح الأدوار، بمجرد تأسيس الاتصال. يمكن أن تستخدم الخدمة أيضًا res.fail() لرفض الاتصال.

لتشغيل handleConnect، انتقل إلى إعدادات معالج الأحداث في مدخل Azure، وحدد الاتصال في أحداث النظام.

تحتاج أيضًا إلى تحديث HTML الخاص بالعميل؛ لأن الخادم الآن يُرسل رسائل JSON بدلًا من نص عادي:

let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
ws.onopen = () => console.log('connected');

ws.onmessage = event => {
  let m = document.createElement('p');
  let message = JSON.parse(event.data);
  switch (message.type) {
    case 'message':
      if (message.group === 'system') m.innerText = `[SYSTEM] ${message.data}`;
      else if (message.group === 'message') m.innerText = `[${message.data.user}] ${message.data.message}`;
      break;
  }
  messages.appendChild(m);
};

let message = document.querySelector('#message');
message.addEventListener('keypress', e => {
  if (e.charCode !== 13) return;
  ws.send(JSON.stringify({
    type: 'event',
    event: 'message',
    dataType: 'text',
    data: message.value
  }));
  message.value = '';
});

ثم تغيير التعليمة البرمجية الخاصة بالعميل للإرسال إلى مجموعة النظام عندما يختار المستخدمون رسالة النظام:

<button id="system">system message</button>
...
<script>
  (async function() {
    ...
    let system = document.querySelector('#system');
    system.addEventListener('click', e => {
      ws.send(JSON.stringify({
        type: 'sendToGroup',
        group: 'system',
        dataType: 'text',
        data: message.value
      }));
      message.value = '';
    });
  })();
</script>

بشكل افتراضي، العميل ليس لديه الإذن لإرسالها إلى أي مجموعة. تحديث التعليمات البرمجية للخادم لمنح الإذن لمستخدم المسؤول (للتبسيط، يتم توفير معرف المسؤول كوسيطة سطر الأوامر).

app.get('/negotiate', async (req, res) => {
  ...
  if (req.user.username === process.argv[2]) options.claims = { role: ['webpubsub.sendToGroup.system'] };
  let token = await serviceClient.getClientAccessToken(options);
});

الآن شغلnode server <admin-id>. ترى أنه يمكنك إرسال رسالة نظام إلى كل عميل عند تسجيل الدخول ك <admin-id>.

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

تغيير التعليمات البرمجية لإرسال رسالة نظام إلى التعليمات البرمجية التالية:

let ackId = 0;
system.addEventListener('click', e => {
  ws.send(JSON.stringify({
    type: 'sendToGroup',
    group: 'system',
    ackId: ++ackId,
    dataType: 'text',
    data: message.value
    }));
  message.value = '';
});

وأيضًا تغيير التعليمات البرمجية لمعالجة الرسائل للتعامل مع رسالة ack:

ws.onmessage = event => {
  ...
  switch (message.type) {
    case 'ack':
      if (!message.success && message.error.name === 'Forbidden') m.innerText = 'No permission to send system message';
      break;
  }
};

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

يمكنك الاطلاع على نموذج التعليمات البرمجية كاملة لهذا البرنامج التعليمي من ⁧GitHub⁩.

الخطوات التالية

يوفر لك هذا البرنامج التعليمي فكرة أساسية عن كيفية الاتصال بخدمة Web PubSub، وكيفية نشر الرسائل إلى العملاء المتصلين باستخدام البروتوكول الفرعي.

لمعرفة المزيد حول استخدام خدمة Web PubSub، اقرأ الدروس الأخرى المتوفرة في الوثائق.