التوجيه الديناميكي بناءً على بيانات اعتماد المستخدم باستخدام API Gateway
April 9, 2023
التوجيه الديناميكي بناءً على مطالبة JWT باستخدام Apache APISIX و Okta
التوجيه الديناميكي هو ميزة قوية في معظم بوابات API الحديثة التي تسمح لك بتوجيه الطلبات الواردة في الوقت الفعلي إلى خدمات خلفية مختلفة بناءً على معايير متنوعة مثل رؤوس HTTP أو معلمات الاستعلام أو حتى جسم الطلب.
من خلال الاستفادة من الإضافات المدمجة الموجودة في Apache APISIX، يمكن للمطورين أيضًا إنشاء قواعد توجيه ديناميكية تعتمد على بيانات اعتماد المستخدم المختلفة مثل رموز الوصول أو مفاتيح API أو معرّفات المستخدم. في هذه المقالة، سنستكشف فوائد اعتماد التوجيه الديناميكي بناءً على سمات المصادقة باستخدام Apache APISIX وسنعرض لك مثالًا على تكوين كيفية توجيه طلبات العملاء بشكل ديناميكي إلى الخدمات الخلفية المسؤولة بناءً على مطالبة JWT.
أهداف التعلم
ستتعلم ما يلي خلال المقالة:
- توجيه حركة المرور بشكل ديناميكي باستخدام بوابة API.
- لماذا نحتاج إلى التوجيه الديناميكي بناءً على بيانات اعتماد المستخدم؟
- التوجيه الديناميكي بناءً على مطالبة JWT باستخدام Apache APISIX.
بوابة API: توجيه حركة المرور بشكل ديناميكي
يمكن استخدام توجيه حركة المرور بشكل ديناميكي مع بوابة API في مجموعة واسعة من التطبيقات والسيناريوهات لتحسين الأداء، وتعزيز الأمان، وضمان وصول المستخدمين إلى الموارد المناسبة.
من خلال توجيه حركة المرور بشكل ديناميكي، يمكن للنظام تحقيق التوازن بين الحمل بين الخوادم أو الخدمات المختلفة. يمكن أن يساعد في ضمان التوفر العالي عن طريق توجيه حركة المرور إلى الخدمات أو الخوادم المتاحة. إذا فشلت خدمة أو خادم معين، يمكن إعادة توجيه حركة المرور تلقائيًا إلى خدمة أو خادم آخر متاح.
يمكن أيضًا استخدام التوجيه الديناميكي لتوجيه حركة المرور بناءً على الموقع الجغرافي للمستخدم. يمكن أن يساعد ذلك في ضمان اتصال المستخدمين بأقرب خادم أو خدمة، مما يحسن أوقات الاستجابة ويقلل من زمن الوصول.
بوابة API: التوجيه الديناميكي بناءً على هوية المستخدم
في كثير من الأحيان، نريد توجيه حركة المرور إلى خدمات أو مسارات محددة أو عرض البيانات المتعلقة بالمستخدم فقط بناءً على الهوية التي يوفرها المستخدم. على سبيل المثال، في التطبيقات متعددة المستأجرين، قد يكون لدى المستأجرين المختلفين وصول إلى خدمات أو موارد مختلفة. في هذه الحالة، يمكن لبوابة API توجيه حركة المرور فقط إلى موارد المستأجر المناسبة بناءً على بيانات اعتماد المستخدم. أو في التطبيقات المحمولة، يمكن توجيه حركة المرور إلى خدمات محددة بناءً على نوع الجهاز أو نظام التشغيل.
إحدى الطرق الشائعة هي استخدام رموز JWT للمصادقة والتفويض للطلبات الموجهة إلى واجهات برمجة التطبيقات. هذا يعني أنه يمكننا إنشاء قواعد توجيه معقدة باستخدام بوابة API تأخذ في الاعتبار المطالبات الموجودة في رمز JWT وتستخدم هذه المعلومات لتحديد مكان إعادة توجيه الطلب أو البيانات التي يجب عرضها. هذا النهج مفيد بشكل خاص عندما يكون لديك مستخدمون متعددون في النظام يحتاجون إلى مستويات مختلفة من التحكم في الوصول.
عرض توضيحي: التوجيه الديناميكي بناءً على مطالبة رمز JWT
في هذا العرض التوضيحي، نستخدم واجهة برمجة التطبيقات الخلفية العامة المسماة Conference API التي تحتوي على معلومات حول جلسات المؤتمر والمتحدثين والموضوعات. في الواقع، يمكن أن تكون هذه خدمتك الخلفية. لنفترض أننا نريد تصفية واسترداد الجلسات التي تنتمي إلى متحدث معين قام بتسجيل الدخول إلى النظام باستخدام بيانات اعتماده مثل رمز JWT. على سبيل المثال، https://conferenceapi.azurewebsites.net/speaker/1/sessions
يعرض الطلب فقط جلسات المتحدث ذو المعرّف الفريد وهذا المعرّف الفريد يأتي من مطالبة رمز JWT كجزء من حمولته. انظر إلى هيكل حمولة الرمز المفكك أدناه، هناك حقل speakerId
مدرج أيضًا:
في هذا السيناريو، نرسل الطلبات إلى نفس المسار في بوابة API وتقوم بحساب URI الديناميكي من رأس التفويض وتعيد توجيه الطلب إلى URI (انظر الرسم البياني أدناه لفهم التدفق). للقيام بذلك، سنقوم بتنفيذ توجيه ديناميكي على مستوى بوابة API Apache APISIX بناءً على مطالبة رمز JWT من خلال استخدام الإضافات التالية:
- openid-connect الإضافة التي تتفاعل مع موفر الهوية (IdP) ويمكنها اعتراض الطلبات غير المصادق عليها في الوقت المناسب للتطبيقات الخلفية. كموفر هوية، نستخدم Okta الذي يصدر رمز JWT مع مطالبتنا المخصصة ويصادق على رمز JWT. أو يمكنك استخدام موفري هوية آخرين مثل Keycloak، و Ory Hydra، أو حتى يمكنك استخدام jwt-plugin لإنشاء رمز JWT، والمصادقة والتفويض للطلبات.
- serverless-pre-function الإضافة لكتابة كود دالة Lua مخصصة تعترض الطلب، وتفكك وتفكك مطالبة رمز JWT وتخزن قيمة المطالبة في رأس مخصص جديد لاتخاذ قرارات التفويض.
- proxy-rewrite الإضافة، بمجرد أن نحصل على المطالبة في الرأس، نستخدم هذه الإضافة كآلية لإعادة توجيه الطلب لتحديد مسار URI الذي يجب استخدامه لاسترداد الجلسات الخاصة بالمتحدث بناءً على متغير رأس Nginx في حالتنا هو
speakerId
الذي يتغير بشكل ديناميكي لإنشاء مسارات مختلفة/speaker/$http_speakerId/sessions
. ستقوم الإضافة بإعادة توجيه الطلب إلى المورد ذي الصلة في Conference API.
بمجرد فهمنا ما سنغطيه خلال العرض التوضيحي، دعنا نتحقق من المتطلبات الأساسية للبدء في تكوين السيناريو أعلاه وإكمال البرنامج التعليمي.
المتطلبات الأساسية
- Docker يستخدم لتثبيت etcd و APISIX المعبأة في حاويات.
- curl يستخدم لإرسال الطلبات إلى APISIX لتكوين المسار، والخادم الخلفي، وإعدادات الإضافات. يمكنك أيضًا استخدام أدوات سهلة مثل Postman للتفاعل مع API.
- Apache APISIX مثبت في بيئتك المستهدفة. يمكن تثبيت APISIX وبدء تشغيله بسهولة باستخدام دليل البدء السريع.
- تأكد من إنشاء حساب OKTA الخاص بك، وقمت بتسجيل تطبيق جديد (يمكنك اتباع هذا الدليل تكوين Okta)، إضافة مطالبة مخصصة إلى الرمز باستخدام لوحة تحكم Okta، و طلب رمز يحتوي على المطالبة المخصصة المسماة
speakerId
.
تكوين الخدمة الخلفية (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
بشكل أساسي، سيتم تنفيذ هذه الإضافة قبل الإضافتين الأخريين وتقوم بما يلي:
- استرداد رمز JWT من رأس التفويض.
- إزالة البادئة "Bearer" من رمز JWT.
- تفكيك رمز JWT باستخدام مكتبة resty.jwt.
- استرداد قيمة مطالبة "speakerId" من رمز JWT المفكك.
- أخيرًا، تخزين قيمة مطالبة "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، وتضمن أن الطلبات المصرح بها فقط هي التي يمكنها الوصول إلى واجهة برمجة التطبيقات الخاصة بك.