التوافر الحديث في .NET: OpenID Connect, BFF, SPA

ومع ما يسار التكنولوجيا الويب في التقدم، تتطور أيضًا طرق وبروتوكولات التأكد المصممة للحماية منها. وقد تطورت بشكل كبير بروتوكولات OAuth 2.0 وOpenID Connect بسبب التهديدات الأمنية الجديدة وتعقيد مزيد للتطبيقات الويبية. ويصبح أساليب التحقق التقليدية، التي كانت فعالة من قبل، قد تعود إلى وراء للتطبيقات الويبية الحديثة الواحدة الصفحة، التي تواجه تحديات أمنية جديدة. وفي هذا السياق، يظهر النموذج الهندسي Backend-For-Frontend (BFF) كحل موصول لتنظيم تفاعلات الأعدادات الأولية مع أنظمة الخلفية الخاصة بها، وهو ما يوفر طريقة أكثر أمانًا وقابلة للإدارة للتحقق وإدارة الدورات. سيستعرض هذا المقال بعمق نموذج BFF، ويظهر تطبيقه عملي من خلال حل بسيط قمت بتنفيذه بواسطة .NET وReact. وبنهاية المقال، سيكون لديك فهم واضح عن كيفية تسخير نموذج BFF لتحسين أمان ووظائف تطبيقاتك الويبية.

السياق التاريخي

تتمثل تاريخ OAuth 2.0 وOpenID Connect في تطور التكنولوجيا الإنترنتية دائمًا. دعونا ننظر بصورة أوسع في هذه البروتوكولات وأثرهم على التطبيقات الويبية الحديثة.

تم إدراج OAuth 2.0 في 2012، وأصبح بروتوكول هذا الإجراء معتبرًا دوليًا واسع الاتخاذ للتصرف التقدمي. يسمح بروتوكول OAuth 2.0 بعدة تداولات، كل منها مصممة لتتكيف مع أساليب استخدام مختلفة.

بناءً على أساس OAuth 2.0، أتضح البروتوكول OpenID Connect (OIDC) في 2014، ويضيف وظائف التحقق الأساسية. يوفر للتطبيقات الزائدة طريقة واحدة واسعة لتحقيق تحقيق الهوية للمستخدم وتحصيل معلوماتهم البسيطة من منبع موحد أو عن طريق احتياج لتوكيل هوية بالصيغة JWT (توكيل على الويب).

تطور نموذج الخطر

مع زيادة قدرات التطبيقات التي تعمل بشكل موحد والشعبية لها، تطور نموذج الخطر لتلك التطبيقات أيضًا. أصبحت الأخطاء مثل التصدير المتعدد المواقعي (XSS) والتخطيط المتعدد المواقعي (CSRF) أكثر شيوعًا. لأن التطبيقات التي تعمل بشكل موحد تتفاعل أغلب الوقت مع الخادم عبر API، لذلك أصبح تخزين واستخدام التوكيلات المتاحة والتحصيلات المتاحة مهمًا للأمان.

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

يمكن الآن أن يعتبر التدفق اللاوحدي المفاهيم من الزمن الماضي، ولأي نوع من الclient، سواء كان هو تطبيق واحد الصفحة، تطبيق هاتفي أو تطبيق مكتبي، وهو الآن موصل بشدة إلى استخدام تدفق المجوز مع مفتاح تحويل المادة (PKCE).

أمن التطبيقات الوحيدة الصفحة الحديثة

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

أخطاء في البرمجيات الجافاسكرية

يعتبر الجافاسكري القوي وهو لغة البرمجيات التي تلعب دوراً رئيسياً في التطبيقات الوحيدة الصفحة الحديثة (SPAs). ومع قدراته الواسعة والشيوعية تتمحور خطر. التطبيقات الوحيدة الصفحة الحديثة المبنية على مجموعات وقواعد مثل React، Vue أو Angular تستخدم عدد كبير من المكتبات والاعتمادات. يمكنك رؤيتهم في المجلد node_modules، وقدر هذه الاعتمادات قد يكون في المئات أو حتى في الآلاف. كل من هذه المكتبات قد يحتوي على أخطاء بمستويات مختلفة من الجدية، ولا يملك المطورون التطبيقات الوحيدة الصفحة القدرة على تفقد البرمجيات لجميع الاعتمادات المستخدمة. غالبًا يتم تتبع القائمة الكاملة للاعتمادات، لأنها تعتمد فيما بينها بشكل تواصلي. حتى وإن قمت بتطوير البرمجيات الخاصة بنا بمعايير جيدة وآمنة جدًا، لا يمكنك أن تتأكد بالكامل من عدم وجود أخطا

يمكن حقن التعليمات البرمجية الضارة في تطبيق بعدة طرق، من خلال هجمات مثل البرمجة النصية عبر المواقع (XSS) أو من خلال اختراق مكتبات الطرف الثالث، وتحصل على نفس الامتيازات ومستوى الوصول إلى البيانات كتعليمات البرمجية الشرعية للتطبيق. هذا يسمح للتعليمات البرمجية الضارة بسرقة البيانات من الصفحة الحالية، والتفاعل مع واجهة التطبيق، وإرسال الطلبات إلى الخلفية، وسرقة البيانات من التخزين المحلي (localStorage، IndexedDB)، وحتى بدء جلسات المصادقة بنفسها، والحصول على رموز الوصول الخاصة بها باستخدام نفس تدفق رمز التفويض وPKCE.

ثغرة Spectre

تستغل ثغرة Spectre ميزات بنية المعالج الحديثة للوصول إلى البيانات التي يجب أن تكون معزولة. هذه الثغرات الأمنية خطيرة بشكل خاص للتطبيقات ذات الصفحة الواحدة (SPAs).

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

تسمح ثغرات Spectre للمهاجمين بسرقة رموز الوصول المخزنة في ذاكرة تطبيق JavaScript، مما يمكّنهم من الوصول إلى الموارد المحمية بانتحال صفة التطبيق الشرعي. يمكن أيضاً استخدام التنفيذ التكهناتي لسرقة بيانات جلسة المستخدم، مما يسمح للمهاجمين بمواصلة هجماتهم حتى بعد إغلاق التطبيق ذو الصفحة الواحدة (SPA).

لا يمكن استبعاد اكتشاف ثغرات أخرى مشابهة لـ Spectre في المستقبل.

ما العمل؟

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

استجابةً للتهديدات المدرجة، يميل المزيد من الخبراء إلى تجنب تخزين الرموز في المتصفح تماماً وتصميم التطبيق بحيث يتم الحصول على رموز الوصول والتحديث ومعالجتها فقط من قبل الجانب الخادمي للتطبيق، ولا يتم تمريرها أبداً إلى جانب المتصفح. في سياق تطبيق SPA مع خلفية، يمكن تحقيق ذلك باستخدام نمط بنية Backend-For-Frontend (BFF).

يبدو مخطط التفاعل بين خادم التفويض (OP) والعميل (RP) الذي ينفذ نمط BFF وواجهة برمجة تطبيقات الطرف الثالث (الخادم الموارد) كالتالي:

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

تطبيق نمط Backend-For-Frontend على منصة .NET

قبل القيام بتنفيذ التطبيق العملي لل BFF على نظام .NET، دعونا نأخذ بالاعتبار مكوناته اللازمة ونخطط لما نحن سنقوم به. لنفترض أن لدينا بالفعل خادم OpenID Connect مconfigurado ونحتاج إلى تطوير SPA يعمل مع الخلفية، نحن سنتطوير التحقق باستخدام OpenID Connect، ونتنظيم التفاعل بين الجزء الخادم والجزء الclient باستخدام النموذج BFF.

ووفقاً للمستند OAuth 2.0 for Browser-Based Applications، يتوجب أن يكون الخلفية تعمل ك client OpenID Connect، وتستخدم التدخل الرقمي مع PKCE للتحقق، وتحصل وتخزين التوكيدات المتاحة والتجديدة على جانبها، ولا تسمح أبداً بتوجيهها إلى جانب SPA الخادم في المتصفح. يتوجب أيضًا أن يكون لدينا وصفة API على جانب الخلفية تأتي منها أربع نقاط النهاية الرئيسية:

  1. Check Session: يستخدم للبحث عن دورة تحقيق التواصل المفعل للمستخدم. عادة ما يطلق من خلال SPA باستخدام API اتجاهي (fetch) و, إذا كان بنجاح، يعود بمعلومات عن المستخدم المفعل. إذا ، يمكن لل SPA ، تحميل من مصدر ثالث (مثل CDN)، أن يتفقد حالة التحقق ويمكنه المضي قدماً إلى تحقيق التحقق باستخدام خادم OpenID Connect.
  2. تسجيل الدخول: يبدأ عملية تحقيق المستخدم المعتمد على الخوادم المتصلة بOpenID Connect. عادةً, إذا فشلت الSPA في تحصيل البيانات المستخدم المؤكدة في الخطوة 1 عن طريق فحص الجلسة, فإنها تقوم بتوجيه المتصفح إلى هذا الURL, ومن ثم تشكل عملية solicitud كاملة إلى خوادم OpenID Connect وتقوم بتوجيه المتصفح إليها.
  3. تسجيل الدخول: تتلقى رمز الترخيص المرسل من الخوادم بعد الخطوة 2 في حالة تم تحقيق التحكم بنجاح. تقوم ب solicitud مباشرة إلى خوادم OpenID Connect لتبادل رمز الترخيص + محاورة تحقيق الرمز لرموز الوصول والتجديد. تبدأ جلسة مستخدم مؤكدة على الجانب المستخدم بتوليد كوكي تحقيق التأكيد للمستخدم.
  4. تسجيل الخروج: يتم به إيقاف الجلسة التحقيقية. عادةً, تقوم الSPA بتوجيه المتصفح إلى هذا الURL, ومن ثم تشكل عملية توجيه المتصفح إلى نقطة الجلسة النهائية على خوادم OpenID Connect لإيقاف الجلسة, ويتم إلغاء الجلسة على الجانب المستخدم وكوكي التحقيق التأكيدي.

الآن دعونا نبحث في أدوات التطوير التي توفر منتديات ال.NET من داخل الحزمة ونرى التفاصيل التي يمكننا استخدامها لتنفيذ نموذج BFF. توفر منتديات ال.NET على حزمة NuGet Microsoft.AspNetCore.Authentication.OpenIdConnect، وهي تطوير جاهز لعميل OpenID Connect مدعوم من قبل مايكروسوفت. تدعم هذه الحزمة التزامات الترخيص الرمزية وPKCE، وتأسيس نقطة النهاية /signin-oidc مب

لمثال عملي للتكامل، سنأخذ مختبر سيرفر برنامج OpenID Connect يستند على المكتبة Abblix OIDC Server. ومع ذلك ينطبق كل ما يتم ذكره أدناه على أي سيرفر آخر، بما في ذلك السيرفرات المتاحة عن صفحات Facebook، Google، Apple وأي آخر يتوافق مع معيار بروتوكول OpenID Connect.

لتنفيذ الSPA على الجانب الأمامي، سنستخدم مكتبة React، وعلى الجانب الخلفي، سنستخدم .NET WebAPI. هذا أحد أكثر التكوينات التقنية الشائعة في الوقت الذي تم كتابة هذه المقالة.

النسق العام للمكونات والتفاعل بينها يبدو مثل هذا:

للعمل مع مثالي المنتجات من هذا المقالة، سيتوجب أيضًا تثبيت ال.NET SDK والNode.js. جميع الأمثلة في هذه المقالة تم تطويرها واختبارها باستخدام .NET 8، Node.js 22 و React 18، وهي الأحدث في الوقت الذي تم كتابة هذه المقالة.

إنشاء SPA العميل في React مع خلفية على .NET

لإنشاء تطبيق المستخدم بسرعة كبيرة، يمكنك استخدام قالب مستعمل. إلى نسخة .NET 7، قدمت ال SDK قالب داخلي لتطبيق .NET WebAPI و React SPA. للأسف، تم إزالة هذا القالب في النسخة .NET 8. لهذا السبب قام فريق Abblix بإنشاء قالب خاص، والذي يشمل .NET WebApi للخلفية، و SPA الجانبي و TypeScript مبني ب Vite. يتم توفير هذا القالب بشكل عام بجزء من الحزمة Abblix.Templates، ويمكنك تثبيته بأمر التواصل التالي:

Shell

 

dotnet new install Abblix.Templates

الآن يمكننا استخدام القالب المسمى abblix-react. دعونا نستخدمه لإنشاء تطبيق جديد يسمى BffSample:

Shell

 

dotnet new abblix-react -n BffSample

هذا الأمر يخلق تطبيق يشمل خلفية .NET WebApi و تطبيق SPA جانبي مبني على React. الملفات المتعلقة بال SPA تقبع في مجلد BffSample\ClientApp.

بعد إنشاء المشروع، سيتم إلتقاء معك للقيام بأمر لتثبيت الاحتياجات:

Shell

 

cmd /c "cd ClientApp && npm install"

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

دعونا نغير معدل البورت الذي سيشغله التطبيق BffSample محلياً إلى 5003. هذه العملية ليست ضرورية، لكنها ستسهل تكوين مختبر ال OpenID Connect. لقيامك بذلك، افتح ملف BffSample\Properties\launchSettings.json، واجد قالب يدعى https وغير قيمة خصيصة applicationUrl إلى https://localhost:5003.

والآن ، قم بإضافة قاعدة NuGet تتم تنفيذ ال client OpenID Connect بالتنقل إلى المجلد BffSample وتنفيذ الأمر التالي:

Shell

 

dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect

تأسيس مخططين للتحقق يدعى Cookies و OpenIdConnect في التطبيق وقراءة إعداداتهم من تكوين التطبيق. لقد تم تعديل ملف BffSample\Program.cs التالي:

C#

 

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();


// ******************* START *******************
var configuration = builder.Configuration;

builder.Services
    .AddAuthorization()
    .AddAuthentication(options => configuration.Bind("Authentication", options))
    .AddCookie()
    .AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));

// ******************** END ********************
var app = builder.Build();

وإضافة إعدادات للاتصال بمستودع الOpenID Connect في ملف BffSample\appsettings.json التالي:

JSON

 

{
  

// ******************* START *******************
  "Authentication": {
      "DefaultScheme": "Cookies",
      "DefaultChallengeScheme": "OpenIdConnect"
  },
  "OpenIdConnect": {
      "SignInScheme": "Cookies",
      "SignOutScheme": "Cookies",
      "SaveTokens": true,
      "Scope": ["openid", "profile", "email"],
      "MapInboundClaims": false,
      "ResponseType": "code",
      "ResponseMode": "query",
      "UsePkce": true,
      "GetClaimsFromUserInfoEndpoint": true
  },
  
// ******************** END ********************
  "Logging": {
    "LogLevel": {
      "Default": "Information",

وفي ملف BffSample\appsettings.Development.json التالي:

JSON

 

{
  

// ******************* START *******************
  "OpenIdConnect": {
      "Authority": "https://localhost:5001",
      "ClientId": "bff_sample",
      "ClientSecret": "secret"
  },
  
// ******************** END ********************
  "Logging": {
    "LogLevel": {
      "Default": "Information",

دعونا ننظر بسرعة في كل إعداد وغرضه:

  • القسم التوثيقي الإتيان: خصم الDefaultScheme يحدد التوثيق بالفعالية باستخدام نموذج Cookies, وDefaultChallengeScheme يتعين على النموذج OpenIdConnect إجراء التوثيق عندما لا يتم توثيق المستخدم بواسطة النموذج الافتراضي. إذن عندما يكون المستخدم غير معروف للتطبيق، سيتم اتصال بالخادم التوثيقي للOpenID، وبعدها، سيتم إعطاء مستخدم متوثق بعملة التوثيق، وجميع المحاولات التالية إلى الخادم ستتم توثيقها بهذه العملية، دون الحاجة إلى اتصال بالخادم التوثيقي مرة أخرى.
  • قسم التعامل المفتوحالجزء:
  • الخصائص SignInScheme و SignOutScheme تحددان الخطة الكوكيز، التي ستستخدم لحفظ معلومات المستخدم بعد التسجيل.
  • تحتوي خصائص السلطة على URL الجدول الأساسي لل服务器 المتصل بالتعامل المفتوح. ClientId و ClientSecret تحددان معرفي التطبيق المستخدم والمفتاح السري ، التي يتم تسجيلها على الserver المتصل بالتعامل المفتوح.
  • SaveTokens توعل الحاجة إلى حفظ التوكينات التي يحصل عليها نتيجة التحقق من قبل server التعامل المفتوح.
  • Scope تحتوي على قائمة بالأنحاء التي يطلب التطبيق BffClient وصول إليها. في هذه الحالة يتم طلب الأنحاء القياسيين openid (معرف المستخدم) و profile (تشخيص الملف الشخصي) و email (البريد الإلكتروني).
  • MapInboundClaims مسئول تحويل المدعومات المتداخلة من server التعامل المفتوح إلى مدعومات تستخدم في التطبيق. يعني قيمة false أن المدعومات ستحفظ في جلسة المستخدم المتوافقة بالشكل الذي يحصل من server التعامل المفتوح.
  • ResponseType بقيمة code تعني أن المستخدم سيستخدم تدفق الرمز التوقعي.
  • ResponseMode تحدد توزيع الرمز التوقعي في الجواري، وهو الطريقة الافتراضية للتدفق التوقعي.
  • UsePkce توعل الحاجة إلى استخدام PKCE أثناء التحقق لمنع سرقة الرمز التوقعي.
  • GetClaimsFromUserInfoEndpoint</code

ولأن تطبيقنا لا يتوجب عليه التفاعل مع المستخدم بدون تحقيق التسجيل المسموح، سنتأكد من تحميل ال SPA بالإثبات الناجح للتسجيل المسموح بالفعل. بالطبع، إذا تم تحميل ال SPA من مصدر خارجي مثل مضيف شبكي ثابت مثل وسائط التوزيع المتوفرة (CDN) أو مختبر تطوير محلي بأوامر التشغيل npm start (على سبيل المثال أثناء تشغيل مثالنا بالتوزيع الفحمي). فلن يكون بإمكانك تحقيق فحص الحالة التسجيلية قبل التحميل ال SPA. لكن حينما يكون خليفتنا ال .NET الخاصة مسؤولة عن تحميل ال SPA، يمكن القيام بذلك.

للقيام بذلك، قم بإضافة الوسيط الوسط المسؤول عن التحقق والتسجيل المسموح في ملف BffSample\Program.cs:

C#

 

app.UseRouting();
// ******************* بداية *******************
app.UseAuthentication();
app.UseAuthorization();
// ******************** نهاية ********************

في نهاية ملف BffSample\Program.cs، حيث يتم تحميل ال SPA بشكل مباشر، أضف الحاجة للتسجيل المسموح، .RequireAuthorization():

C#

 

app.MapFallbackToFile("index.html").RequireAuthorization();

Configuring the OpenID Connect Server

وكما ذكرنا مسبقًا ، للمثال العملي للتركيب العملي سنستخدم مخزن تجريبي للتواصل المتعلق بOpenID Connect البنية على Abblix OIDC Server library. تemplates البنية الأساسية لتطبيق يعتمد على ASP.NET Core MVC مع الAbblix OIDC Server library يمكن الحصول عليها في قاعدة Abblix.Templates التي تم تحميلها من قبل. دعونا نستخدم هذه القالب لإنشاء تطبيق جديد يدعى OpenIDProviderApp:

Shell

 

dotnet new abblix-oidc-server -n OpenIDProviderApp

لتكوين المخزن ، يتوجب علينا تسجيل BffClient التطبيق كعميل في المخزن المتعلق بOpenID Connect وإضافة مستخدم تجريبي. لذلك قم بإضافة الجداول التالية إلى ملف OpenIDProviderApp\Program.cs:

C#

 

var userInfoStorage = new TestUserStorage(
    // ******************* START *******************
    new UserInfo(
        Subject: "1234567890",
        Name: "John Doe",
        Email: "[email protected]",
        Password: "Jd!2024$3cur3")
    // ******************** END ********************
);
builder.Services.AddSingleton(userInfoStorage);

// ...

// تسجيل وتكوين Abblix OIDC Server
builder.Services.AddOidcServices(options =>
{
    // تكوين خيارات مخزن OIDC هنا:
    // ******************* START *******************
    options.Clients = new[] {
        new ClientInfo("bff_sample") {
            ClientSecrets = new[] {
                new ClientSecret {
                    Sha512Hash = SHA512.HashData(Encoding.ASCII.GetBytes("secret")),
                }
            },
            TokenEndpointAuthMethod = ClientAuthenticationMethods.ClientSecretPost,
            AllowedGrantTypes = new[] { GrantTypes.AuthorizationCode },
            ClientType = ClientType.Confidential,
            OfflineAccessAllowed = true,
            PkceRequired = true,
            RedirectUris = new[] { new Uri("https://localhost:5003/signin-oidc", UriKind.Absolute) },
            PostLogoutRedirectUris = new[] { new Uri("https://localhost:5003/signout-callback-oidc", UriKind.Absolute) },
        }
    };
    // ******************** END ********************
    // يوجد تلك الرابطة توليد إجراء تسجيل الدخول للمراقب التعلمي
    options.LoginUri = new Uri($"/Auth/Login", UriKind.Relative);

    // السطر التالي يولد مفتاح جديد لتوجيه التواصل. إستبدله إذا أردت استخدام أساسا خاص بك.
    options.SigningKeys = new[] { JsonWebKeyFactory.CreateRsa(JsonWebKeyUseNames.Sig) };
});

لنقرأ هذا الكود بشكل دقيق. نسجل مستخدماً بالمعرفين bff_sample والمفاتيح السرية secret (نتخزنها ك哈希 SHA512), ونقوم بإشارة إلى أن التسجيل للتوكن سيستخدم التوثيق المستخدم مع المفتاح السري الذي يتم إرساله في رسالة POST (ClientAuthenticationMethods.ClientSecretPost). AllowedGrantTypes يحدد أن المستخدم مسموح باستخدام تدور التوكن الموجه. ClientType يحدد المستخدم كخاص، ما يعني أنه يمكن تخزين بأمان مفتاحه السري. OfflineAccessAllowed يسمح للمستخدم باستخدام التوكنات التجديدية. PkceRequired يتم إجبار الاستخدام من خلال الPKCE أثناء التحقق. RedirectUris و PostLogoutRedirectUris تحتوي على قوائم من الURL المسموح بهم للتوجيه بعد التحقق وإنهاء الجلسة، بشكل مماز.

لأي مخزن تواصل OpenID Connect آخر، سيكون هناك تشابهات في إعداداتهم، مع اختلافات فقط في كيفية تكوينها.

تنفيذ منحنى BFF API البسيط

ومن قبل ذكرنا أن استخدام الحزمة Microsoft.AspNetCore.Authentication.OpenIdConnect يضم بشكل تلقائي التنفيذ لنقطة الدخول الأولي إلى تطبيقنا الأساسي. الآن يوجد الوقت لتنفيذ جزء بقي المنحنى BFF API. سنستخدم متحكم ASP.NET MVC لهذه النقاط الإضافية المتوفرة. دعونا نبدأ بإضافة مجلد Controllers وملف BffController.cs في مشروع BffSample مع التعبير التالي:

C#

 

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace BffSample.Controllers;

[ApiController]
[Route("[controller]")]
public class BffController : Controller
{
    public const string CorsPolicyName = "Bff";

    [HttpGet("check_session")]
    [EnableCors(CorsPolicyName)]
    public ActionResult> CheckSession()
    {
        // تعطي 401 غير مسموح للتوجيه التقني لل SPA الى نقطة الدخول المسموحة
        if (User.Identity?.IsAuthenticated != true)
            return Unauthorized();

        return User.Claims.ToDictionary(claim => claim.Type, claim => claim.Value);
    }

    [HttpGet("login")]
    public ActionResult> Login()
    {
        // المبدأ العقلي لبدء تداوم الشيكل التوجيهي
        return Challenge(new AuthenticationProperties { RedirectUri = Url.Content("~/") });
    }

    [HttpPost("logout")]
    public IActionResult Logout()
    {
        // المبدأ العقلي لمعالجة خروج المستخدم
        return SignOut();
    }
}

دعونا نتجزأ هذه الكود القاعدي بشكل دقيق:

  • الصفته [Route("[controller]")] تضبط المسار الأساسي لجميع الأعمال في المعالج. في هذه الحالة ، سيتطابق المسار بإسم المعالج، ويعني أن جميع مسارات إلى وظائف ال API التي نحن نطلق عليها ستبدأ بـ /bff/.
  • المعاملة الثابتة CorsPolicyName = "Bff" تحدد اسم السياسة الخاصة بـ CORS (مشاركة مصادر بعد المناطق) للاستخدام في خصائص الأعمال. سنذكرها في المرة القادمة.
  • تتم تنفيذ الثلاث أشياء CheckSession و Login و Logout تحقيق الوظائف الضرورية لل BFF التي توصفت أعلاه. إنهم يتم إدارة solicitudes GET في /bff/check_session ، /bff/login و solicitudes POST في /bff/logout بشكل منفصل.
  • تقوم الطريقة CheckSession بتحقيق حالة تحقيق المستخدم. إذا لم يتم تحقيق المستخدم، فإنها تعطي رقم 401 Unauthorized ، وهذا يجب أن يجعل ال SPA يتم توجيهه الى نقطة الدخول المسموحة. إذا تم تحقيق التحقيق بنجاح، فإن الطريقة تعطي مجموعة من الأصول وقيمتها. وهذه الطريقة تشمل أيضًا تابعة سياسة CORS بإسم CorsPolicyName لأن مصادر المعالجة قد تكون متعددة المناطق وتحتوي على كوكبيات تستخدم لتحقيق التحكم في المستخدم
  • يتم استدعاء طريقة تسجيل الدخول من قبل الSPA إذا عادت تحقق من الجلسة السابقة توفر 401 غير مسموح. وهي تتأكد أن المستخدم لا يتم تحقيق تصحيحه بعد وتبدأ بعملية تحدي الموجودة في الإعدادات، وسينتج عن ذلك توجيه إلى 服务器 الOpenID Connect، تسجيل الدخول باستخدام تدوير الرمز التقني وPKCE، وتمرير عملية تسجيل البوكي. بعد هذا، سيعود ال control إلى جذر تطبيقنا "~/"، وسيدفع الSPA لتحميل مجدد وبدء التطبيق مع مستخدم متأكد.
  • يتم أيضًا استدعاء طريقة تسجيل الخروج من قبل الSPA لكنه يُنهي الجلسة التامة الحالية للتحقيق. يحذف أكواب التحقيق تم إصدارها من جزء ال服务器 من BffSample ويتم أيضًا دعوة النقطة النهائية للجلسة على جانب ال服务器 OpenID Connect.

تكوين CORS ل BFF

وكما ذكرنا أعلاه، توجه الطريقة تحقق من الجلسة يتم إستخدامه لمكالمات أسية من الSPA (عادةً باستخدام الAPI Fetch). بالإمكانية السليمة لهذه الطريقة تعتمد على قدرة المتصفح على إرسال البوكي التحقيقي. إذا تم تحميل الSPA من مضغوط ويب بشكل منفصل، مثل CDN أو مضغوط تطوير يعمل في منابع مختلفة، يصبح هذه المكالمة خارجة عن المجال. وهذا يجعل تكوين سياسة CORS ضرورية، بدونها لن يتمكن الSPA من استدعاء هذه الطريقة.

لقد أشيرنا بالموجودة في تنسيق المرشد في ملف Controllers\BffController.cs إلى أن سيا

C#

 


// ******************* بدأ *******************
using BffSample.Controllers;
// ******************** نهاية ********************
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

// ...

builder.Services
    .AddAuthorization()
    .AddAuthentication(options => configuration.Bind("Authentication", options))
    .AddCookie()
    .AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));
// ******************* بدأ *******************
builder.Services.AddCors(
    options => options.AddPolicy(
        BffController.CorsPolicyName,
        policyBuilder =>
        {
            var allowedOrigins = configuration.GetSection("CorsSettings:AllowedOrigins").Get();

            if (allowedOrigins is { Length: > 0 })
                policyBuilder.WithOrigins(allowedOrigins);

            policyBuilder
                .WithMethods(HttpMethods.Get)
                .AllowCredentials();
        }));
// ******************** نهاية ********************
var app = builder.Build();

هذا الكود يسمح لأساليب سياسة CORS بالاتصال من تطبيقات الSPA المحمولة من مصادر محددة في الت配置 كمجموعة من السطور CorsSettings:AllowedOrigins، باستخدام الطريقة GET ويسمح للكوكبيات التي تم إرسالها في هذا الاتصال. بالإضافة إلى ذلك، تأكد من أن طلب ال app.UseCors(...) يوضع بجانب ال app.UseAuthentication():

C#

 

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// ******************* بدأ *******************
app.UseCors(BffController.CorsPolicyName);
// ******************** نهاية ********************
app.UseAuthentication();
app.UseAuthorization();

لضمان أن سياسة CORS تعمل بطريقة correct, add the corresponding setting to the BffSample\appsettings.Development.json configuration file:

JSON

 

{
  // ******************* بدأ *******************
  "CorsSettings": {
    "AllowedOrigins": [ "https://localhost:3000" ]
  },
  // ******************** نهاية ********************
 "OpenIdConnect": {
   "Authority": "https://localhost:5001",
   "ClientId": "bff_sample",

في مثالنا، يوجد عنوان https://localhost:3000 حيث يتم تشغيل مستودع التطوير بواسطة الSPA المتكامل من خلال أمر npm run dev. يمكنك إيجاد هذا العنوان في حالتك بفتح ملف BffSample.csproj وبحث عن قيمة ما يسمى SpaProxyServerUrl. في تطبيق حقيقي، قد تشمل سياسة CORS عنوان خدمة التوزيع المحتويات (CDN) أو خدمة مماثلة. ومن المهم أن تتذكر أنه إذا كان تطبيق الSPA يتم تحميله من عنوان ومنبثق مختلف عن المنبثق الذي يقدم الAPI BFF، عليك إضافة هذا العنوان إلى تعيين سياسة CORS.

تنفيذ التحقيق عبر BFF في تطبيق React

لقد تنفيذنا ال API BFF على الجانب السيرفري. الآن يوم التركيز على ال SPA React وإضافة الممارسات المتعلقة للمكالم بهذا ال API. دعونا نبدأ بالتنقل إلى مجلد BffSample\ClientApp\src\، وإنشاء مجلد components، وإضافة ملف Bff.tsx مع التحديد التالي:

TypeScript

 

import React, { createContext, useContext, useEffect, useState, ReactNode, FC } from 'react';

// تعريف شكل الcontext للBFF
interface BffContextProps {
    user: any;
    fetchBff: (endpoint: string, options?: RequestInit) => Promise;
    checkSession: () => Promise;
    login: () => void;
    logout: () => Promise;
}

// إنشاء سياق للBFF لمشاركة الحالة والوظائف في التطبيق
const BffContext = createContext({
    user: null,
    fetchBff: async () => new Response(),
    checkSession: async () => {},
    login: () => {},
    logout: async () => {}
});

interface BffProviderProps {
    baseUrl: string;
    children: ReactNode;
}

export const BffProvider: FC = ({ baseUrl, children }) => {
    const [user, setUser] = useState(null);

    // تنظيف الURL الأساسي بإزالة الشرف الختامي لتجنب الURL الغير متوازية
    if (baseUrl.endsWith('/')) {
        baseUrl = baseUrl.slice(0, -1);
    }

    const fetchBff = async (endpoint: string, options: RequestInit = {}): Promise => {
        try {
            // تضمين الوظيفة التقاط المعلومات الشهيرة للمساعدة على معالجة الكوكبيات، التي تتطلب للتعرف الرسمي
            return await fetch(`${baseUrl}/${endpoint}`, {
                credentials: 'include',
                ...options
            });
        } catch (error) {
            console.error(`Error during ${endpoint} call:`, error);
            throw error;
        }
    };

    // توجيه الوظيفة التسجيل الدخول إلى الصفحة التسجيلية عندما يتوجب على المستخدم التحقق
    const login = (): void => {
        window.location.replace(`${baseUrl}/login`);
    };

    // الوظيفة التحقق من ال sesión تسمح لها بتحقيق الجلسة المستخدمية في التشكيل الأولي
    const checkSession = async (): Promise => {
        const response = await fetchBff('check_session');

        if (response.ok) {
            // إذا كانت الجلسة مؤكدة، تحديد الحالة المستخدمية مع البيانات المستلمة
            setUser(await response.json());
        } else if (response.status === 401) {
            // إذا كان المستخدم غير متوافق، تقوم بتوجيهه إلى الصفحة التسجيلية
            login();
        } else {
            console.error('Unexpected response from checking session:', response);
        }
    };

    // وظيفة تسجيل الخروج من المستخدم
    const logout = async (): Promise => {
        const response = await fetchBff('logout', { method: 'POST' });

        if (response.ok) {
            // توجيه إلى الصفحة الرئيسية بعد التسجيل الموافق
            window.location.replace('/');
        } else {
            console.error('Logout failed:', response);
        }
    };

    // useEffect يستخدم لتشغيل وظيفة التحقق من الجلسة حين تستند المكون
    // هذا يضمن أن يتم تحقيق الجلسة بمجرد تحميل التطبيق
    useEffect(() => { checkSession(); }, []);

    return (
        // تقدم السياق الBFF مع القيم المتعلقة والوظائف للاستخدام في جميع أجزاء التطبيق
        
            {children}
        
    );
};

// المقبل المخصص لاستخدام السياق الBFF بسهولة في المكونات الأخرى
export const useBff = (): BffContextProps => useContext(BffContext);

// تصدير HOC لتوفير وصول إلى سياق BFF
export const withBff = (Component: React.ComponentType) => (props: any) =>
    
        {context => }
    ;

يتم تصدير هذا الملف:

  • المكون BffProvider الذي يخلق سياق الBFF ويقدم الوظائف والحالة المتعلقة بالتعرف وإدارة الجلسة للتطبيق بأكمله
  • المسار التقني useBff() الخاص يعطي عنصرًا يحوي حالة المستخدم الحالية والوظائف للعمل مع BFF: checkSession, login, و logout. وهو يخص الاستخدام في المكونات الرقمية الفاعلة الرائدة.
  • المكون الأعلى التقني (HOC) withBff للاستخدام في المكونات التي تبني من التعامل التقني التقليدي.

من ثم، قم بإنشاء مكون UserClaims الذي سيعرض معاملات المستخدم الحالي بنجاح التحقق. قم بإنشاء ملف UserClaims.tsx في مجلد BffSample\ClientApp\src\components والمحتويات التالية:

TypeScript

 

import React from 'react';
import { useBff } from './Bff';

export const UserClaims: React.FC = () => {
    const { user } = useBff();

    if (!user)
        return <div>Checking user session...</div>;

    return (
        <>
            <h2>User Claims</h2>
            {Object.entries(user).map(([claim, value]) => (
                <div key={claim}>
                    <strong>{claim}</strong>: {String(value)}
                </div>
            ))}
        </>
    );
};

هذا البرمجيات تفقد لمستخدم موجود بواسطة المسار التقني useBff() وتعرض معاملات المستخدم كقائمة إذا كان المستخدم معروف. إذا كانت بيانات المستخدم لا توفر بعد، ستعرض النص Checking user session....

الآن، دعونا ننتقل إلى ملف BffSample\ClientApp\src\App.tsx. استبدال محتوياته ببرمجيات الضرورية. قم بتصدير BffProvider من components/Bff.tsx و UserClaims من components/UserClaims.tsx، ثم أدرج برمجيات المكون الرئيسي:

TypeScript

 

import { BffProvider, useBff } from './components/Bff';
import { UserClaims } from './components/UserClaims';

const LogoutButton: React.FC = () => {
    const { logout } = useBff();
    return (
        <button className="logout-button" onClick={logout}>
            Logout
        </button>
    );
};

const App: React.FC = () => (
    <BffProvider baseUrl="https://localhost:5003/bff">
        <div className="card">
            <UserClaims/>
        </div>
        <div className="card">
            <LogoutButton />
        </div>
    </BffProvider>
);

export default App;

في هذه الأجزاء، ي especifies ما يلي المادة baseUrl بواسطة ما يلي الرابط https://localhost:5003/bff لباسة توافر البرنامج الرئيسي BFF API. هذا التبسيط معني ومقصده وهو بسيط. في تطبيق حقيقي، يجب توفير هذا الإعداد بشكل ديناميكي بدلاً عن كتابته من أجل البساطة. وهناك طرق متعددة لتحقيق هذا، ولكن المناقشة خارج مجال هذه المقالة.

يتيح ال按钮 تسجيل الخروج للمستخدم إلخروج المستخدم. يسمح له بالطريقة التي يتم بها استخدام المادة useBff للاتصال بالوظيفة logout ويقوم بتوجيه متصفح المستخدم إلى نقطة النهاية /bff/logout، وهذا ينهي جلسة المستخدم عند الجهة الخارجية للخوادم.

في هذه المرحلة يمكنك أن تقوم بتشغيل تطبيق BffSample مع تطبيق OpenIDProviderApp وتجربة قدراتها. يمكنك استخدام أوامر التشغيل dotnet run -lp https في كل مشروع أو محرك تطويرك المفضل لإنشاءهما. يتوجب عمل كلا التطبيقين معاً في الوقت المعتاد.

بعد هذا، قم بفتح متصفحك الإنترنتي وتوجه إلى https://localhost:5003. إذا تم إعداد الأمور بطريقة صحيحة، سيتم تحميل الSPA وسيطرد المتصفح إلى /bff/check_session. سيعود نقطة النهاية /check_session بردة 401، متركبة لتوجيه المتصفح إلى /bff/login، وسيبدأ هذا في التعيين على الخوادم عن طريق تسلسل التفويض الرقمي للتوجيه بواسطة OpenID Connect باستخدام PKCE. يمكنك مراقبة تسلسل ال solicitudes بفتح لوحة التطوير في متصفحك الإنترنتي والذهاب إلى لوحة الشبكة. بعد التسجيل الناجح لمعلومات المستخدم ([email protected]، Jd!2024$3cur3)، سيعود ال control إلى الSPA، وسترون معلومات المستخدم المتوفرة في المتصفح:

Plain Text

 

sub: 1234567890
sid: V14fb1VQbAFG6JXTYQp3D3Vpa8klMLcK34RpfOvRyxQ
auth_time: 1717852776
name: John Doe
email: [email protected]

أيضًا، عند ملاكمة ال按钮 تسجيل الخروج سيتم توجيه المتصفح إلى /bff/logout، وهذا يسجل المس

إذا وجدت أي أخطأ، يمكنك مقارنة خوادمك مع مخزوننا على GitHub Abblix/Oidc.Server.GettingStarted، الذي يحتوي على هذه الأمثلة وغيرها مستعدة للتشغيل.

حل مشاكل ثقة الشهادات HTTPS

عندما تختبر التطبيقات الويب المضافة الى التشغيل بواسطة HTTPS محليًا، قد تواجه تحذيرًا للمتصفح بأن الشهادة SSL غير موثوقة. ينشأ هذه المشكلة لأن شهادات التطوير التي يستخدمها ASP.NET Core لم تصدر من وكالة تصميم معتمدة (CA) ولكنها موجودة بصفة موجودة بالمجال الخاص أو تحتوي على مجال أولي. يمكن التخلص من تلك التحذيرات بتنفيذ الأمر التالي:

Shell

 

dotnet dev-certs https --trust

هذا الأمر يولد شهادة موجودة بصفة موجودة بالمجال الخاص لـlocalhost وينصبها في نظامك لكي يثق بهذه الشهادة. سيستخدم ASP.NET Core هذه الشهادة لتشغيل التطبيقات الويب المحلية. بعد تنفيذ هذا الأمر، أعد متصفحك لكي تأخذ تلك التغيرات في نفسها.

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

  • أنقر في أي مكان على صفحة الخطأ وأكتب thisisunsafe أو badidea وفقًا لإصدار Chrome الذي تعامل به. تلك

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

الاتصال بAPI الأطراف الثالثة عن طريق BFF

لقد نجحنا في تنفيذ التحقق في تطبيقنا BffSample. والآن دعونا نستمر إلى الاتصال بAPI الثالثة التي تتطلب توكيل.

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

إنشاء الخدمة ApiSample

قبل التوثيق للاتصال بالAPI البعيد عن طريق BFF، يتوجب علينا إنشاء تطبيق سيؤدي إلى هذه الAPI في مثالنا.

سنستخدم قالب من .NET لإنشاء التطبيق. تحويل إلى المجلد الحالي يحوي الOpenIDProviderApp وBffSample المشاريع، وتنفيذ تلك الأمرارة لإنشاء ApiSample التطبيق:

Shell

 

dotnet new webapi -n ApiSample

هذا التطبيق الأساسي ل ASP.NET Core Minimal API يقدم نقطة النهاية واحدة بالمسار /weatherforecast والتي توفر بيانات الطقس بالتنسيق JSON.

في البدء، تغير رقم المنفذ المعطا عشوائيا لتطبيق ApiSample الذي يستخدم محلياً لرقم منفذ ثابت، 5004. وكما ذكرنا مسبقاً، هذه الخطوة غير إلزامية، ومعها تبسيط إعداداتنا. للقيام بذلك، افتح ملف ApiSample\Properties\launchSettings.json وابحث عن التشريح المسمى https وتغيير قيمة خصيصة applicationUrl إلى https://localhost:5004.

والآن لنجعل الAPI الجوي قابل للوصول بالفحص بواسطة توكين. تحويل إلى مجلد المشروع ApiSample وإضافة حزمة NuGet لتحقيق التوكين بواسطة توكين التوكين:

Shell

 

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

قم بت配置 النظام التعييسي وسياسة التفويض تدعى WeatherApi في ملف ApiSample\Program.cs:

C#

 

// ******************* بداية *******************
using System.Security.Claims;
// ******************** نهاية ********************
var builder = WebApplication.CreateBuilder(args);

// إضافة خدمات إلى المجال.
// تعلم أكثر عن تكوين Swagger/OpenAPI على https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ******************* بداية *******************
var configuration = builder.Configuration;

builder.Services
    .AddAuthentication()
    .AddJwtBearer(options => configuration.Bind("JwtBearerAuthentication", options));

const string policyName = "WeatherApi";

builder.Services.AddAuthorization(
    options => options.AddPolicy(policyName, policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireAssertion(context =>
        {
            var scopeValue = context.User.FindFirstValue("scope");
            if (string.IsNullOrEmpty(scopeValue))
                return false;

            var scope = scopeValue.Split(' ', StringSplitOptions.RemoveEmptyEntries);
            return scope.Contains("weather", StringComparer.Ordinal);
        });
    }));
// ******************** نهاية ********************
var app = builder.Build();

يتم تكوين التعييس بقراءة التكوينات من 设置 التطبيق، بموجبها تشمل التفويض باستخدام JWT (أشعة توكينية حقيقية)، وتكوين سياسة التفويض تدعى WeatherApi. توظيف سياسة WeatherApi يحدد التالي:

  • policy.RequireAuthenticatedUser(): تضمن إمكانية متابعة موارد محمية فقط للمستخدمين المتوافقين.
  • سياسة.توجب تأكيد الادعاء(context => ...): يتوجب على المستخدم أن يمتلك مدخلاً scope يشمل القيمة weather. ومن ثم لأن مدخل scope يمكن أن يحتوي على أقيمة متعددة منفصلة بواسطة الفارقات التي توفر RFC 8693, يتم تفرقة قيمة scope الحقيقية إلى أجزاء منفصلة، ويتم فحص التواريخ الناتجة لتحدد وجود القيمة المطلوبة weather.

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

يتوجب علينا تطبيق هذه السياسة على نقطة النهاية /weatherforecast. أضف دعوة المراجعة RequireAuthorization() كما يظهر أدناه:

C#

 

app.MapGet("/weatherforecast", () =>
{

// ...

})
.WithName("GetWeatherForecast")
// ******************* بدء *******************
.WithOpenApi()
.RequireAuthorization(policyName);
// ******************** نهاية ********************

أضف إعدادات التعيين الضرورية للنظام التعريفي في ملف appsettings.Development.json لتطبيق ApiSample:

JSON

 

{
  // ******************* بدء *******************
  "JwtBearerAuthentication": {
    "Authority": "https://localhost:5001",
    "MapInboundClaims": false,
    "TokenValidationParameters": {
      "ValidTypes": [ "at+jwt" ],
      "ValidAudience": "https://localhost:5004",
      "ValidIssuer": "https://localhost:5001"
    }
  },
  // ******************** نهاية ********************
  "Logging": {
    "LogLevel": {
      "Default": "Information",

دعونا ننظر في كل إعداد بدقة:

  • Authority: هذا الURL التي توجد إلى مخزن التوكيل المفتوح الرابط يصدر رسائل الJWT. سيستخدم المزود التعريفي المعين في تطبيق ApiSample هذا الURL للحصول على المعلومات التي تحتاج للتحقق من الرسائل، مثل مفاتيح التوقيع.
  • تعيين MapInboundClaims: ي控制 كيفية توجيه المدخلات المتسلسلة من الجواز التعريفي (JWT) إلى المدخلات الداخلية في ASP.NET Core. وهو معدل false, مما يعني سيستخدم المدخلات أسماءها الأصلية من الJWT.
  • TokenValidationParameters:
    • ValidTypes: يعيد إعداد إلى at+jwt, وهو وفقا ل RFC 9068 2.1 يشير إلى توكيل للموافقة بالتشكيل الJWT.
    • ValidAudience: يحدد أن التطبيق سيقبل رموز منتشرة للمستخدم https://localhost:5004.
    • ValidIssuer: يحدد أن التطبيق سيقبل رموز منتشرة من خلال الخوادم https://localhost:5001.

تكوينات إضافية ل OpenIDProviderApp

تعامل مزيج الخدمة التعقيدية OpenIDProviderApp مع التطبيق الزائد BffSample يعمل جيدا لتوفير تعقيد التحقق بالمستخدمين. مع ذلك ، لتمكين الاتصال بالAPI البعيد، يجب علينا تسجيل التطبيق ApiSample كمورد مع OpenIDProviderApp. في مثالنا، نستخدم Abblix OIDC Server ، الذي يدعم RFC 8707: مؤشرات الموردين لـ OAuth 2.0. لذلك سنقوم بتسجيل ApiSample كمورد مع النطاق weather. إذا كان لديك خدمة OpenID Connect أخرى لا تدعم المؤشرات الموردية، يمكن أيضًا التوصيل بنطاق فريد لهذه الAPI البعيدة (مثل weather في مثالنا).

أضف ال following code إلى ملف OpenIDProviderApp\Program.cs:

C#

 

// تسجيل وتنظيم Abblix OIDC Server
builder.Services.AddOidcServices(options => {
    // ******************* بدء *******************
    options.Resources =
    [
        new(new Uri("https://localhost:5004", UriKind.Absolute), new ScopeDefinition("weather")),
    ];
    // ******************* نهاية *******************
    options.Clients = new[] {
        new ClientInfo("bff_sample") {

في هذا المثال، نقوم بتسجيل تطبيق ApiSample، مع تحديد عنوانه الأساسي https://localhost:5004 كمورد وتحديد نطاق معين باسم weather. في التطبيقات الواقعية، وخاصة تلك التي تحتوي على واجهات برمجة تطبيقات معقدة تتكون من العديد من النقاط النهائية، يُنصح بتعريف نطاقات منفصلة لكل نقطة نهائية فردية أو مجموعة من النقاط النهائية ذات الصلة. يتيح هذا النهج تحكمًا أكثر دقة في الوصول ويوفر مرونة في إدارة حقوق الوصول. على سبيل المثال، يمكنك إنشاء نطاقات مميزة للعمليات المختلفة، أو لوحدات التطبيق، أو لمستويات وصول المستخدمين، مما يتيح تحكمًا أكثر تفصيلًا حول من يمكنه الوصول إلى أجزاء معينة من واجهة برمجة التطبيقات الخاصة بك.

شرح BffSample للاستخدام كوكيل لطلبات إلى واجهة برمجة تطبيقات عن بُعد

يحتاج تطبيق العميل BffSample الآن إلى القيام بأكثر من مجرد طلب رمز وصول لـ ApiSample. يجب عليه أيضًا معالجة الطلبات من SPA إلى واجهة برمجة التطبيقات البعيدة. يتضمن ذلك إضافة رمز الوصول الذي تم الحصول عليه من خدمة OpenIDProviderApp إلى هذه الطلبات، وإرسالها إلى الخادم البعيد، ثم إعادة ردود الخادم إلى SPA. في جوهره، يحتاج BffSample إلى العمل كخادم وكيل عكسي.

بدلاً من تنفيذ الوكالة للطلبات يدويًا في تطبيق العميل الخاص بنا، سنستخدم YARP (وكيل عكسي آخر) وهو منتج جاهز تم تطويره بواسطة Microsoft. YARP هو خادم وكيل عكسي مكتوب بلغة .NET ومتوفر كحزمة NuGet.

لاستخدام YARP في تطبيق BffSample، أضف أولاً حزمة NuGet:

Shell

 

dotnet add package Yarp.ReverseProxy

ثم أضف مساحات الأسماء التالية في بداية الملف BffSample\Program.cs:

C#

 

using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Net.Http.Headers;
using Yarp.ReverseProxy.Transforms;

قبل المكالمة var app = builder.Build();, أضف البرمجيات:

C#

 

builder.Services.AddHttpForwarder();

وبين المكالمات لapp.MapControllerRoute() و app.MapFallbackToFile():

C#

 

app.MapForwarder(
    "/bff/{**catch-all}",
    configuration.GetValue("OpenIdConnect:Resource") ?? throw new InvalidOperationException("Unable to get OpenIdConnect:Resource from current configuration"),
    builderContext =>
    {
        // أزل المسافة "/bff" من مسافة الطلب
        builderContext.AddPathRemovePrefix("/bff");

        builderContext.AddRequestTransform(async transformContext =>
        {
            // حصل على التوكيسر تلقى مسبقًا أثناء عملية التحقق
            var accessToken = await transformContext.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            
            // أضف معهد مع التوكيسر المتوفر في المعالجة التوجيهية
            transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        });
    }).RequireAuthorization();

دعونا نفكر في ما يفعل هذا البرمجيات:

  • builder.Services.AddHttpForwarder() يسجل خدمات YARP الضرورية في الحزب التكاملي.
  • app.MapForwarder تأسيس توجيه المعاداة إلى خادم آخر أو نقطة نهاية.
  • "/bff/{**catch-all}" هي نمط المسافة الذي سيستجيب له مرور العقبي. جميع المعادات التي تبدأ ب/bff/ستتم تنظيمها بواسطة YARP. {**catch-all} يستعمل للمسافة الباقية بعد /bff/ في الURL.
  • configuration.GetValue<string>("OpenIdConnect:Resource") يستخدم التكوينات التي للتطبيق للحصول على قيمة من القسم OpenIdConnect:Resource. قيمة هذه القسم تعني عنوان المورد الذي سيتم توجيه المعادات إليه. في مثالنا، سيكون قيمة https://localhost:5004 – URL الجديد الأساسي حيث تعمل تطبيق ApiSample.
  • builderContext => ... يضيف التحويلات الضرورية التي سيقوم YARP بإجرائها على كل طلب وارد من SPA. في حالتنا، سيكون هناك تحويلان:
    • builderContext.AddPathRemovePrefix("/bff") يزيل البادئة /bff من مسار الطلب الأصلي.
    • builderContext.AddRequestTransform(async transformContext => ...) يضيف ترويسة HTTP Authorization إلى الطلب، تحتوي على رمز الوصول الذي تم الحصول عليه سابقًا أثناء المصادقة. وبالتالي، سيتم توثيق الطلبات من SPA إلى API البعيد باستخدام رمز الوصول، على الرغم من أن SPA نفسها لا تمتلك الوصول إلى هذا الرمز.
  • .RequireAuthorization() يحدد أن التفويض مطلوب لجميع الطلبات المعاد توجيهها. سيتمكن فقط المستخدمون المصرح لهم من الوصول إلى المسار /bff/{**catch-all}.

لتقديم توكين وصول للمورد https://localhost:5004 خلال التحقق، قم بإضافة ما بين الماركات Resource بقيمة https://localhost:5004 إلى التكوين OpenIdConnect في الملف BffSample/appsettings.Development.json:

JSON

 

  "OpenIdConnect": {
    // ******************* بدء *******************
    "Resource": "https://localhost:5004",
    // ******************** نهاية ********************
    "Authority": "https://localhost:5001",
    "ClientId": "bff_sample",

أيضًا، قم بإضافة قيمة أخرى weather إلى القائمة scope في الملف BffSample/appsettings.json:

JSON

 

{
  "OpenIdConnect": {

    // ...

    // ******************* بدء *******************
    "Scope": ["openid", "profile", "email", "weather"],
    // ******************** نهاية ********************

    // ...

  }
}

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

الطلب من الAPI الجوي عبر BFF في تطبيق SPA

يتم إعداد الخلفية الآن. لدينا تطبيق ApiSample الذي يتم تنفيذه وهو يحصل على وصول بواسطة توكين وصولي والتحقق يدعمه وتطبيق BffSample الذي يشمل خوادم المنبثقة المدمجة لتوفير وصول آمن إلى هذه الAPI. والخطوة الأخيرة هي إضافة الممارسة التي تتطلب هذا الAPI وعرض البيانات التي تحصل عليها ضمن التطبيق SPA الرياضي.

أضف ملف WeatherForecast.tsx في BffSample\ClientApp\src\components مع المحتويات التالية:

TypeScript

 

import React, { useEffect, useState } from 'react';
import { useBff } from './Bff';

interface Forecast {
    date: string;
    temperatureC: number;
    temperatureF: number;
    summary: string;
}

interface State {
    forecasts: Forecast[];
    loading: boolean;
}

export const WeatherForecast: React.FC = () => {
    const { fetchBff } = useBff();
    const [state, setState] = useState<State>({ forecasts: [], loading: true });
    const { forecasts, loading } = state;

    useEffect(() => {
        fetchBff('weatherforecast')
            .then(response => response.json())
            .then(data => setState({ forecasts: data, loading: false }));
    }, [fetchBff]);

    const contents = loading
        ? <p><em>Loading...</em></p>
        : (
            <table className="table table-striped" aria-labelledby="tableLabel">
                <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Temp. (F)</th>
                    <th>Summary</th>
                </tr>
                </thead>
                <tbody>
                {forecasts.map((forecast, index) => (
                    <tr key={index}>
                        <td>{forecast.date}</td>
                        <td align="center">{forecast.temperatureC}</td>
                        <td align="center">{forecast.temperatureF}</td>
                        <td>{forecast.summary}</td>
                    </tr>
                ))}
                </tbody>
            </table>
        );

    return (
        <div>
            <h2 id="tableLabel">Weather forecast</h2>
            <p>This component demonstrates fetching data from the server.</p>
            {contents}
        </div>
    );
};

دعونا نفك الكود:

  • يحدد الشبكة Forecast تشكيل بيانات التنبؤ الجوي ، وهو يشمل التاريخ ، درجة الحرارة بالمئة والفahrenheit وخلفية مختصرة للطقس. يصف الشبكة State تشكيل الحالة الخاصة بالمكون ، وهي تكون من قاعدة من تنبؤات الطقس وعلامة تحميل.
  • المكون WeatherForecast يحصل على وظيفة fetchBff من خانة useBff ويستخدمها لتحميل بيانات الطقس من الخادم. تتبع الحالة الموجودة في المكون بواسطة خانة useState ، تبدأ بقاعدة خالية من التنبؤات وعلامة تحميل تم تعيينها للصواب.
  • ويتم تشغيل خانة useEffect عندما يتم تثبيت المكون ، تتسبب في تشغيل وظيفة fetchBff لتحميل بيانات التنبؤ الجوي من الخادم عند النقطة /bff/weatherforecast. حينما يتلقى المستجيب من الخادم ويتم تحويله إلى JSON ، يتم تخزين البيانات في الحالة الموجودة في المكون (من خلال setState) وتحديد false للعلامة التحميل.
  • وبناءً على قيمة العلامة التحميل، يعرض المكون رسالة “يتم تحميل…” أو يرسم جدولاً ببيانات التنبؤ الجوي. الجدول يشمل أعمدتين للتاريخ ودرجة الحرارة بالمئة والفahrenheit وخلفية مختصرة للطقس لكل تنبؤ.

الآن أضف المكون WeatherForecast إلى BffSample\ClientApp\src\App.tsx:.

TypeScript

 

// ******************* بدء *******************
import { WeatherForecast } from "./components/WeatherForecast";
// ******************** نهاية ********************

// ...

    
// ******************* بدء *******************
// ******************** نهاية ********************
   

التنفيذ والاختبار

إذا كان جميع الأمور قد أجريت، يمكنك الآن بدء جميع ثلاثة مشاريعنا. قام باستخدام أوامر التحكم في الكونسول dotnet run -lp https لكل تطبيق لتشغيله بالHTTPS.

بعد بدء جميع ثلاثة التطبيقات، فتح تطبيق BffSample في متصفحك (https://localhost:5003) وتأكيد الدخول باسم المستخدم [email protected] وكلمة المرور Jd!2024$3cur3. بعد التأكد الموفق على الدخول، ينبغي أن ترى قائمة بالأمانين التي تلقت من خادم التأكيد الأمني، كما رأيناه من قبل. أسفل هذه، سترى أيضًا التنبوءات الجوية.

توفر التنبوءات الجوية من تطبيق منعزل ApiSample، الذي يستخدم شخصية توكيل تم إصداره من خدمة التأكيد الأمني OpenIDProviderApp. رؤية التنبوءات الجوية في نافذة تطبيق BffSample تعني أن تطبيق our SPA تم اتصاله بخدمة الخلفية بنجاح، ومن ثم قام بتوكيل المكالمة إلى ApiSample بإضافة الشخصية التوكيلية. ApiSample تحقق من المكالمة ويستجيب بJSON يحوي التنبوءات الجوية.

يتم توفير الحل بأكمله على GitHub.

إذا وجدت أي مشاكل أو خطأ أثناء تنفيذ المشاريع التجريبية، يمكنك الاستعمال للحل كامل المتوفر في المستودع GitHub. ببساطة قم بتنسيق المستودع Abblix/Oidc.Server.GettingStarted للوصول إلى المشاريع المتميزة والتي يوصف في هذا المقال. يعتبر هذا المورد أداة للمعالجة المشاكل ومنتج مبني جيد لبدء مشاريعك الخاصة.

الخلاصة

تطور بروتوكولات التحقق مثل OAuth 2.0 و OpenID Connect يعكس توجهات واسعة في مجال الأمن الويب وقدرات المتصفحات. التحول إلى طرق أكثر أمانة مثل تدخل الرمز مع الPKCE قد تحسين الأمان بشكل كبير. ومع ذلك، تميز تواجد الأشياء في بيئات غير م控制ة يجعل تأمين مشاريع الSPA الحديثة مهمة صعبة. تخزين التوكينات بالإستناد exclusivity على الخلفية واتخاذ النموذج الخلفية للأمامية (BFF) هي استراتيجية فعالة لتخفيف الخطرات وضمان حماية البيانات المستخدمة قوية.

يتوجب على المطورين الابتداع في توجيه الخطرات المتغيرة بتنفيذ طرق التحقق الجديدة والأساليب الهيكلية المتوفرة بالمعلومات. هذا الماهية المبنية واسعة هي أساس قوي لمشاريعك المستقبلية. في هذه المقالة، قمنا بإستكشاف وتنفيذ طريقة حديثة لتوافير OpenID Connect، BFF و SPA باستخدام توالي تقنيات .NET و React المشهورة. يمكن أن تكون هذه الطريقة قاعدة قوية لمشاريعك المستقبلية.

وأنه بمناسبة المستقبل، سيتطلب التطور المستمر في أمن الشبكة إبداعات أكبر في التحقق والأنماط الهيكلية. نشجبكم أن تستكشفوا مستودعنا على GitHub، وتقدموا لتطوير حلول تحقق حديثة وتبقوا مع تقدمات الأيام. شكراً للإهتمامكم!

Source:
https://dzone.com/articles/modern-authentication-on-dotnet