التوجيه الديناميكي بناءً على بيانات اعتماد المستخدم باستخدام API Gateway

Bobur Umurzokov

Bobur Umurzokov

April 9, 2023

Technology

التوجيه الديناميكي بناءً على مطالبة JWT باستخدام Apache APISIX و Okta

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

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

أهداف التعلم

ستتعلم ما يلي خلال المقالة:

  • توجيه حركة المرور بشكل ديناميكي باستخدام بوابة API.
  • لماذا نحتاج إلى التوجيه الديناميكي بناءً على بيانات اعتماد المستخدم؟
  • التوجيه الديناميكي بناءً على مطالبة JWT باستخدام Apache APISIX.

بوابة API: توجيه حركة المرور بشكل ديناميكي

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

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

توجيه حركة المرور بشكل ديناميكي باستخدام Apache APISIX

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

توجيه حركة المرور بناءً على الموقع الجغرافي باستخدام Apache APISIX

بوابة API: التوجيه الديناميكي بناءً على هوية المستخدم

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

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

توجيه حركة المرور بشكل ديناميكي بناءً على رمز JWT باستخدام Apache APISIX

عرض توضيحي: التوجيه الديناميكي بناءً على مطالبة رمز JWT

في هذا العرض التوضيحي، نستخدم واجهة برمجة التطبيقات الخلفية العامة المسماة Conference API التي تحتوي على معلومات حول جلسات المؤتمر والمتحدثين والموضوعات. في الواقع، يمكن أن تكون هذه خدمتك الخلفية. لنفترض أننا نريد تصفية واسترداد الجلسات التي تنتمي إلى متحدث معين قام بتسجيل الدخول إلى النظام باستخدام بيانات اعتماده مثل رمز JWT. على سبيل المثال، https://conferenceapi.azurewebsites.net/speaker/1/sessions

يعرض الطلب فقط جلسات المتحدث ذو المعرّف الفريد وهذا المعرّف الفريد يأتي من مطالبة رمز JWT كجزء من حمولته. انظر إلى هيكل حمولة الرمز المفكك أدناه، هناك حقل speakerId مدرج أيضًا:

رمز JWT مع مطالبة مخصصة

في هذا السيناريو، نرسل الطلبات إلى نفس المسار في بوابة API وتقوم بحساب URI الديناميكي من رأس التفويض وتعيد توجيه الطلب إلى URI (انظر الرسم البياني أدناه لفهم التدفق). للقيام بذلك، سنقوم بتنفيذ توجيه ديناميكي على مستوى بوابة API Apache APISIX بناءً على مطالبة رمز JWT من خلال استخدام الإضافات التالية:

  1. openid-connect الإضافة التي تتفاعل مع موفر الهوية (IdP) ويمكنها اعتراض الطلبات غير المصادق عليها في الوقت المناسب للتطبيقات الخلفية. كموفر هوية، نستخدم Okta الذي يصدر رمز JWT مع مطالبتنا المخصصة ويصادق على رمز JWT. أو يمكنك استخدام موفري هوية آخرين مثل Keycloak، و Ory Hydra، أو حتى يمكنك استخدام jwt-plugin لإنشاء رمز JWT، والمصادقة والتفويض للطلبات.
  2. serverless-pre-function الإضافة لكتابة كود دالة Lua مخصصة تعترض الطلب، وتفكك وتفكك مطالبة رمز JWT وتخزن قيمة المطالبة في رأس مخصص جديد لاتخاذ قرارات التفويض.
  3. proxy-rewrite الإضافة، بمجرد أن نحصل على المطالبة في الرأس، نستخدم هذه الإضافة كآلية لإعادة توجيه الطلب لتحديد مسار URI الذي يجب استخدامه لاسترداد الجلسات الخاصة بالمتحدث بناءً على متغير رأس Nginx في حالتنا هو speakerId الذي يتغير بشكل ديناميكي لإنشاء مسارات مختلفة /speaker/$http_speakerId/sessions. ستقوم الإضافة بإعادة توجيه الطلب إلى المورد ذي الصلة في Conference API.

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

المتطلبات الأساسية

تكوين الخدمة الخلفية (upstream)

ستحتاج إلى تكوين الخدمة الخلفية لـ Conference API التي تريد توجيه الطلبات إليها. يمكن القيام بذلك عن طريق إضافة خادم خلفي في Apache APISIX من خلال واجهة برمجة التطبيقات الإدارية.

curl "http://127.0.0.1:9180/apisix/admin/upstreams/1" -X PUT -d '
{
  "name": "Conferences API upstream",
  "desc": "Register Conferences API as the upstream",
  "type": "roundrobin",
  "scheme": "https",
  "nodes": {
    "conferenceapi.azurewebsites.net:443": 1
  }
}'

إنشاء تكوين الإضافة

بعد ذلك، نقوم بإعداد كائن تكوين إضافة جديد. سنستخدم 3 إضافات openid-connect، serverless-pre-function و proxy-rewrite على التوالي كما ناقشنا حالات استخدام كل إضافة سابقًا. تحتاج فقط إلى استبدال سمات إضافة openid-connect (ClienID, Secret, Discovery and Introspection endpoints) بتفاصيل Okta الخاصة بك قبل تنفيذ أمر curl.

curl "http://127.0.0.1:9180/apisix/admin/plugin_configs/1" -X PUT -d '
{
    "plugins": {
        "openid-connect":{
            "client_id":"{YOUR_OKTA_CLIENT_ID}",
            "client_secret":"{YOUR_OKTA_CLIENT_SECRET}",
            "discovery":"https://{YOUR_OKTA_ISSUER}/oauth2/default/.well-known/openid-configuration",
            "scope":"openid",
            "bearer_only":true,
            "realm":"master",
            "introspection_endpoint_auth_method":"https://{YOUR_OKTA_ISSUER}/oauth2/v1/introspect",
            "redirect_uri":"https://conferenceapi.azurewebsites.net/"
        },
        "proxy-rewrite": {
            "uri": "/speaker/$http_speakerId/sessions",
            "host":"conferenceapi.azurewebsites.net"
        },
        "serverless-pre-function": {
            "phase": "rewrite",
            "functions" : ["return function(conf, ctx)

    -- استيراد المكتبات الضرورية
    local core = require(\"apisix.core\")
    local jwt = require(\"resty.jwt\")

    -- استرداد رمز JWT من رأس التفويض
    local jwt_token = core.request.header(ctx, \"Authorization\")
    if jwt_token ~= nil then
        -- إزالة البادئة "Bearer" من رمز JWT
        local _, _, jwt_token_only = string.find(jwt_token, \"Bearer%s+(.+)\")
        if jwt_token_only ~= nil then
           -- تفكيك رمز JWT
           local jwt_obj = jwt:load_jwt(jwt_token_only)

           if jwt_obj.valid then
             -- استرداد قيمة مطالبة "speakerId" من رمز JWT
             local speakerId_claim_value = jwt_obj.payload.speakerId

             -- تخزين قيمة مطالبة "speakerId" في متغير الرأس
             core.request.set_header(ctx, \"speakerId\", speakerId_claim_value)
           end
         end
     end
   end
    "]}
    }
}'

في التكوين أعلاه، قد يكون الجزء الأصعب في الفهم هو كود الدالة المخصص الذي كتبناه بلغة Lua داخل إضافة serverless-pre-function:

return function(conf, ctx)
    -- استيراد المكتبات الضرورية
    local core = require(\"apisix.core\")
    local jwt = require(\"resty.jwt\")

    -- استرداد رمز JWT من رأس التفويض
    local jwt_token = core.request.header(ctx, \"Authorization\")
    if jwt_token ~= nil then
        -- إزالة البادئة "Bearer" من رمز JWT
        local _, _, jwt_token_only = string.find(jwt_token, \"Bearer%s+(.+)\")
        if jwt_token_only ~= nil then
           -- تفكيك رمز JWT
           local jwt_obj = jwt:load_jwt(jwt_token_only)

           if jwt_obj.valid then
             -- استرداد قيمة مطالبة "speakerId" من رمز JWT
             local speakerId_claim_value = jwt_obj.payload.speakerId

             -- تخزين قيمة مطالبة "speakerId" في متغير الرأس
             core.request.set_header(ctx, \"speakerId\", speakerId_claim_value)
           end
         end
   end
end

بشكل أساسي، سيتم تنفيذ هذه الإضافة قبل الإضافتين الأخريين وتقوم بما يلي:

  1. استرداد رمز JWT من رأس التفويض.
  2. إزالة البادئة "Bearer" من رمز JWT.
  3. تفكيك رمز JWT باستخدام مكتبة resty.jwt.
  4. استرداد قيمة مطالبة "speakerId" من رمز JWT المفكك.
  5. أخيرًا، تخزين قيمة مطالبة "speakerId" في متغير الرأس speakerId.

تكوين مسار جديد

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

curl "http://127.0.0.1:9180/apisix/admin/routes/1"  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "name":"Conferences API speaker sessions route",
    "desc":"Create a new route in APISIX for the Conferences API speaker sessions",
    "methods": ["GET"],
    "uri": "/sessions",
    "upstream_id":"1",
    "plugin_config_id":1
}'

في التكوين أعلاه، قمنا بتحديد قواعد مطابقة المسار مثل توجيه طلبات HTTP GET فقط إلى URI /sessions إلى الخدمة الخلفية الصحيحة.

الحصول على رمز من Okta

بعد تكوين الخادم الخلفي والإضافات والمسار على جانب APISIX، نطلب الآن رمزًا من Okta يحتوي على مطالبتنا المخصصة speakerId. يمكنك اتباع الدليل الذي يتضمن معلومات حول بناء عنوان URL لطلب رمز مع Okta أو ببساطة استخدام عنوان URL الناتج أدناه مع مُصدر Okta ومعرّف العميل الخاص بك:

https://{YOUR_OKTA_ISSUER}/oauth2/default/v1/authorize?client_id={YOUR_OKTA_CLIENT_ID}
&response_type=id_token
&scope=openid
&redirect_uri=https%3A%2F%2Fconferenceapi.azurewebsites.net
&state=myState
&nonce=myNonceValue

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

https://conferenceapi.azurewebsites.net/#id_token={TOKEN_WILL_BE_HERE}

لاحظ أن عملية استرداد الرمز يمكن أن تختلف عن موفري الهوية الآخرين.

للتحقق من رمز التعريف الذي تم إرجاعه، يمكنك نسخ القيمة ولصقها في أي مفكك لرموز JWT (على سبيل المثال، https://token.dev).

اختبار التوجيه الديناميكي

أخيرًا، يمكننا الآن التحقق من أن الطلب يتم توجيهه إلى مسار URI الصحيح (مع الجلسات الخاصة بالمتحدث) بناءً على معايير المطابقة ومطالبة رمز JWT عن طريق تشغيل أمر curl بسيط آخر:

curl -i -X "GET [http://127.0.0.1:9080/sessions](http://127.0.0.1:9080/sessions)" -H "Authorization: Bearer {YOUR_OKTA_JWT_TOKEN}"

ها نحن ذا، النتيجة كما توقعنا. إذا قمنا بتعيين speakerId إلى 1 في مطالبة JWT الخاصة بـ Okta، قام Apisix بتوجيه الطلب إلى مسار URI ذي الصلة وإرجاع جميع جلسات هذا المتحدث في الاستجابة.

{
  "collection": {
    "version": "1.0",
    "links": [],
    "items": [
      {
        "href": "https://conferenceapi.azurewebsites.net/session/114",
        "data": [
          {
            "name": "Title",
            "value": "\r\n\t\t\tIntroduction to Windows Azure Part I\r\n\t\t"
          },
          {
            "name": "Timeslot",
            "value": "04 December 2013 13:40 - 14:40"
          },
          {
            "name": "Speaker",
            "value": "Scott Guthrie"
          }
        ],
        "links": [
          {
            "rel": "http://tavis.net/rels/speaker",
            "href": "https://conferenceapi.azurewebsites.net/speaker/1"
          },
          {
            "rel": "http://tavis.net/rels/topics",
            "href": "https://conferenceapi.azurewebsites.net/session/114/topics"
          }
        ]
      },
      {
        "href": "https://conferenceapi.azurewebsites.net/session/121",
        "data": [
          {
            "name": "Title",
            "value": "\r\n\t\t\tIntroduction to Windows Azure Part II\r\n\t\t"
          },
          {
            "name": "Timeslot",
            "value": "04 December 2013 15:00 - 16:00"
          },
          {
            "name": "Speaker",
            "value": "Scott Guthrie"
          }
        ],
      }
    ]
   }
}

النقاط الرئيسية

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