أعدّ بوّابة الوكيل مع nginx
بنهاية هذا الدرس ستكون البوّابة تعمل أمام موقع خلف nginx: زائر غير مُمرَّر يُرسَل إلى لعبة، ولعبة محلولة تضبط كوكي، وطلبات لاحقة تمرّ مباشرةً. ستراقب الأمر كلّه أولًا في وضع المعاينة، فلا يُحجَب شيء حتى تؤكّد التوصيل. يُستخدَم هنا nginx؛ ووصفات Traefik وCaddy وAuthelia تتبع الشكل نفسه.
1. شغّل وضع المعاينة
في لوحة التحكّم، افتح الفريق وشغّل وضع المعاينة. وطوال تشغيله، يسجّل مُخوِّل البوّابة قرارات التحدّي مقابل المرور المباشر في صفحة الإحصاءات لكنه لا يحجب أبدًا. ستطفئه في النهاية.
2. فعّل البوّابة على مفتاح موقعك
افتح مفتاح موقعك واذهب إلى صفحة بوّابة الوكيل. شغّل الحالة ← مفعّل، ثم اضبط:
- مدّة السماح TTL: كم من الوقت يمرّر حلٌّ واحد زائرًا (افتراضيًّا 30 دقيقة). الأقصر يعني فحوصًا أكثر لكن نافذة أصغر إن سُرِق كوكي.
- وضع الفشل: ما يفعله وكيلك حين لا يستطيع بلوغ مُخوِّل Caputchin. الفشل المغلق (fail closed) يحجب (أأمن لبوّابة تسجيل دخول)؛ والفشل المفتوح (fail open) يمرّر الطلبات (يتفادى التوقّف على موقع عام).
تعرض الصفحة أيضًا مقتطف nginx جاهزًا للنسخ ومفتاح موقعك مملوء فيه سلفًا.
3. اسمح بالأصل الذي تغلقه ببوّابة
التحدّي لا يعيد التوجيه إلا إلى أصل مسموح، القائمة نفسها للأصول المسموحة التي تستخدمها الأداة. تأكّد أنها تشمل الأصل الذي تغلقه، مثلًا https://auth.example.com. اضبطه في إعداد Cap لمفتاح الموقع إن لم يكن موجودًا سلفًا.
4. صِل المُخوِّل وإعادة التوجيه إلى التحدّي
كتلتا location: طلب فرعي مُخوِّل داخلي، وإعادة توجيه تحدٍّ يقفز إليها الوكيل عند 401. استبدِل YOUR_SITE_KEY بمفتاحك العامّ.
# Gate the protected paths.
location / {
auth_request /_cpt_authz; # 204 = allow, 401 = challenge
error_page 401 = @cpt_challenge;
# ...proxy_pass to your app...
}
location = /_cpt_authz {
internal;
proxy_pass https://verify.caputchin.com/v1/gate/authz?site=YOUR_SITE_KEY;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Cookie $http_cookie; # forward the cpt_gate cookie
}
location @cpt_challenge {
return 302 https://verify.caputchin.com/v1/gate/challenge?site=YOUR_SITE_KEY&return=$scheme://$host$request_uri;
}أغلِق مسارات الدخول ببوّابة (HTML البوّابة وPOST تسجيل الدخول). لا تغلق الطلب الفرعي للمُخوِّل، ولا فحوص السلامة، ولا ردّ النداء أدناه.
5. أضِف ردّ النداء الذي يضبط الكوكي
تنتهي صفحة التحدّي بإرسال ذاتي لـ POST صغير إلى /__cpt/callback على أصلك، مع cpt_gate (التصريح) وto (إلى أين يُرسَل الزائر) في الجسم. مُعالِج ضئيل يضبط الكوكي ويعيد التوجيه. العقد الكامل، بما فيه لِمَ تهمّ كل سمة كوكي، في وصفات الوكيل العكسي. الحدّ الأدنى:
app.post("/__cpt/callback", express.urlencoded({ extended: false }), (req, res) => {
const pass = String(req.body.cpt_gate || "");
const to = String(req.body.to || "/");
const target = new URL(to, `https://${req.headers.host}`);
if (target.host !== req.headers.host) return res.status(400).end(); // same-origin only
res.setHeader(
"Set-Cookie",
`cpt_gate=${pass}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1800`
);
res.redirect(303, target.pathname + target.search);
});6. راقبها في المعاينة
حمّل الرابط المغلق. لأن المعاينة مشغّلة، تمرّ مباشرةً، لكن صفحة الإحصاءات تسجّل challenge_served (لم يكن لديك كوكي) و، حالما تحلّ ويعمل ردّ النداء، pass_issued. أعِد التحميل ويُفترَض أن ترى passthrough يتسلّق. إن لم تظهر التحدّيات أبدًا، فكتلة المُخوِّل لا تُلمَس؛ وإن ردّ ردّ النداء بـ 400، فافحص أصل to.
7. انطلِق حيًّا
حين تبدو الأرقام صحيحة، أطفئ وضع المعاينة. تفرض البوّابة الآن: الزوّار غير المُمرَّرين يُرسَلون إلى اللعبة، والزوّار الذين حلّوا يتجوّلون حتى ينتهي تصريحهم.
إلى أين بعد ذلك
- وصفات الوكيل العكسي: Traefik، وCaddy، وشرح Authelia الكامل.
- إحصاءات بوّابة الوكيل: ما الذي يعنيه كل رقم حالما تصير حيًّا.
- نظرة عامة: الفكرة وما الذي لا تفعله البوّابة عمدًا.