ومرة أخرى ، يعرف اختبار CAPTCHA أو nginx أيضًا كيفية التطريز

مقدمة


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


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


الشروط


لمنع الروبوتات من تنزيل الملفات ، يتم استخدام اختبار "captcha".


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


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


يوجد وكيل يعيد توجيه جميع الطلبات إلى الواجهة الخلفية.


المشاكل


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


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


قرار


اختيار وظيفة


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


<img src="data:image/png;base64,{{ IMAGE CODE BASE64 }}"> <input type="hidden" name="key" value="{{ KEY }}"> 

أو JSON:


 { ... "data": { "image": "data:image/png;base64,{{ IMAGE CODE BASE64 }}", "key": "{{ KEY }}" } } 

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


 ssi on; 

وفي رمز صفحة النموذج ، نقوم بإدراج:


 ... <form action="download" method="get" ...> ... <!--#include virtual="/x/captcha/generate"--> ... </form> ... 

وبالتالي ، قمنا بتخصيص وظيفة تعيين captcha في موقع أو طرق منفصلة. الآن يمكنك القيام بالتخزين المؤقت.


نعم ، يتم نسيان آلية تضمين جانب الخادم (SSI) تقريبًا ، لكن وحدة nginx الخاصة بها أكثر حيوية من جميع الكائنات الحية وتعمل بسرعة كبيرة. وبالمناسبة ، في حالة قيام proxy_pass_cache بتخزين الصفحة بالكامل مؤقتًا ، فلن يتم تخزين نتيجة التضمين الظاهري ، ولكن سيتم تنفيذها في كل مرة يتم طلبها. هذا يسمح لك لجعل الإدراج ديناميكية.

CAPTCHA CAPTCHA


لتنفيذ التخزين المؤقت ، نحتاج إلى شيء عشوائي تمامًا ويتحكم فيه عدد الخيارات ، المتغير $ request_id مناسب لهذا الدور - إنه عشوائي تمامًا وعشري ، أي باختيار جزء معين من هذا المتغير ، يمكنك قصر عدد عناصر ذاكرة التخزين المؤقت على 16 ^ n ، حيث n - عدد الحروف التي نحتاج أن نأخذها من المتغير. لذلك:


تحديد منطقة ذاكرة التخزين المؤقت:


 proxy_cache_path /cache/nginx/captcha levels=1:1 keys_zone=captcha:10m max_size=128m; 

من المهم تحديد قيمة n التي نختارها ، على التوالي ، تعتمد المعلمات على هذا:


  • المستويات = 1: 2
  • الحجم الأقصى = 128 متر
  • keys_zone = captcha: 10m

كان ذلك كافيا لكل شيء ، لكن لم يكن هناك شيء غير ضروري. بعد ذلك ، نحدد مفتاح ذاكرة التخزين المؤقت:


 server { ... set $captcha_salt 'salt'; if ( $request_id ~* "(\w{4})$" ) { set $cache_key $1; } ... 

لا يزال المتغير $ captcha_salt مفيدًا لنا ، ولكنه الآن يحمي من التقاطعات الرئيسية المحتملة. لقد اخترت n كـ 4 ، مما يعني فتحات ذاكرة التخزين المؤقت 16 ^ 4 ، مع تخصيص 2 كيلو بايت في المتوسط ​​لكل فتحة من إجمالي حجم ذاكرة التخزين المؤقت ( max_size = 128m ) ، وهو ما ينبغي أن يكون كافيًا ، وإلا فستحتاج إلى رفع الحد الأقصى للحجم.


جعل الموقع المناسب


 location /x/captcha/generate { proxy_cache captcha; proxy_cache_key "$captcha_salt:$cache_key"; proxy_cache_valid 200 365d; proxy_cache_valid any 0s; proxy_set_header Host "captcha.service.domain.my"; proxy_pass http://captcha_upstream/?cache_key=$cache_key; } 

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


بالمناسبة ، يمكن استخدام آلية مشابهة لإنشاء ديناميات زائفة عندما يضغط المستخدم على F5 وفي كل مرة يتم عرض "صورة" عشوائية جديدة له. في هذه الحالة ، لم يتم تحميل الواجهة الخلفية عمليًا.

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


 location /x/captcha/cache/purge { internal; proxy_cache_purge captcha "$captcha_salt:$arg_cache_key"; } 

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


أي أننا نقدم طلبًا موازًا لإعادة تعيين ذاكرة التخزين المؤقت بواسطة مفتاح المتغير $ arg_cache_key ، والذي يتم إرساله في النموذج. بعد ذلك ، فقط أرسل الطلب إلى الواجهة الخلفية حيث يتم تنفيذ بقية المعالجة.


الطريقة الشائكة للتحسين


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


المهمة التي بقيت مع الخادم من حيث التحقق من captcha هي في الواقع التحقق من رمز المفتاح + وإزالة هذا الزوج من المستودع. يمكن أن يكون التحقق من رمز المفتاح + بمثابة مقارنة بسيطة لمبلغ md5 بالمفتاح. لهذا ، الوحدة النمطية كافية بالنسبة لنا: http_secure_link_module . بمعنى أنه يمكن تمثيل المفتاح كصيغة:


key = md5_baseurl( salt + code )

في الوقت نفسه ، لن يؤذيك الربط بفتحة التخزين المؤقت (مفتاح ذاكرة التخزين المؤقت) ، بل نضيفها:


key = md5_baseurl( salt + code + cache_key )

لدينا الملح - هذا هو المتغير $ captcha_salt (لذلك أصبح مفيدًا) ، ولكن الاحتفاظ بالملح في مكانين من الواجهة الخلفية والوكيل سيئ ، لذلك دعونا نفعل هذا:


 location /x/captcha/salt { allow {{ captcha backend IPs }}; deny all; return 200 "$captcha_salt"; } 

ودع الخلفية تذهب إلى الوكيل للملح.


يبقى السؤال مع المستودع ، حيث نقوم بتخزين زوج رمز + مفتاح يحتاج إلى تنظيف. للقيام بذلك ، فإن آلية التخزين المؤقت التي قمنا بتنفيذها بالفعل مناسبة لنا. الشيء الوحيد هو أننا لا نعالج نتيجة cache_purge بأي شكل من الأشكال ، ولكن ببساطة عكس الطلب الخاص بها ، ولكن هذا قابل للتثبيت. ونعم ، هذا يبرر استخدام مفتاح ذاكرة التخزين المؤقت عند إنشاء مفتاح captcha.


رمز الاختيار


أعد كتابة تنزيلات ملفات الموقع :


 location /download { proxy_set_header Host $host; proxy_set_header X-Context download; proxy_set_header X-File-Name $arg_filename; proxy_set_header X-Key $arg_key; proxy_set_header X-Code $arg_code; proxy_set_header X-Cache-Key $arg_cache_key; proxy_pass http://127.0.0.1/x/captcha/check; proxy_intercept_errors on; error_page 403 404 = /download/fail; } 

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


  • خطأ التحقق من رمز 403. في الواقع ، لذلك ، يتم تضمين proxy_intercept_errors ويتم الإعلان عن موقع لإعادة التوجيه في حالة حدوث خطأ ؛
  • 404 - خطأ في تنظيف ذاكرة التخزين المؤقت. الوحدة النمطية cache_purge إذا كان هناك شيء في ذاكرة التخزين المؤقت مع مثل هذا المفتاح بإرجاع 404؛
  • 200 + Accel-Redirect - على موقع تحميل الملف ، في حال تمت عملية التحقق من صحة اختبار captcha. في حالتنا ، سيكون X-Accel-Redirect: / store / file

إذا كان error_page يمكن أن يتعامل مع رموز 2XX ، يمكن للمرء أن يفعل ذلك وحده. وإلا ، فأنت بحاجة إلى استخدام آلية Accel-Redirect . إذا كنت ترغب حقًا في ذلك ، يمكنك فصل معالجات الأخطاء 403 و 404 ؛

ارتكاب خطأ بسيط في الموقع :


 location /download/fail { internal; return 200 "FAIL DOWNLOAD"; } 

يمكنك إرجاع أي شيء في هذا الموقع ، حسب احتياجاتك.


نجعل موقع تحميل الملف:


 location /store/file { internal; add_header Content-Disposition "attachment; filename=\"$arg_filename\""; alias /spool/tmp/; try_files $arg_filename =404; } 

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


الموقع التالي لدينا للتحقق من captcha:


 location /x/captcha/check { allow 127.0.0.1; deny all; secure_link_md5 "$captcha_salt$http_x_code$http_x_cache_key"; secure_link $http_x_key; if ($secure_link = "") { return 403 "FAIL CHECK CODE"; } proxy_set_header Host $host; proxy_pass http://127.0.0.1/x/captcha/purge; } 

إنه يحتوي على كتلتين: التحقق من الرمز والوكيل الإضافي لمسح ذاكرة التخزين المؤقت. في نفس الوقت ، إذا لم ينجح التحقق من الكود ، فقم بإرجاع 403 على الفور (النص غير مهم ، لأنه لم يتم استخدامه أكثر).


سيعيد الوكلاء إلى / x / captcha / purge خيارين للإجابة:


  • 200 + Accel-Redirect - عند مسح ذاكرة التخزين المؤقت بنجاح. ستكون إعادة التوجيه على X-Accel-Redirect: / x / captcha / check / ok ؛
  • 404 - إذا لم يكن هناك شيء للتنظيف. سيتم تمرير هذه النتيجة أعلاه إلى / تنزيل وسيتم معالجتها فيه error_page ؛

يتم إجراء معالج منفصل للاستجابة الإيجابية من / x / captcha / purge نظرًا لأننا أولاً بحاجة إلى الوصول إلى مستوى أعلى من البروكسي ، وليس بين / download و / x / captcha / check . ثانياً ، سيكون من الجيد إعطاء إجابتك الإيجابية فيما يتعلق بالسياق.


لنبدأ بمعالج استجابة إيجابية:


 location /x/captcha/check/ok { internal; if ( $http_x_context = 'download' ) { add_header X-Accel-Redirect "/store/file?filename=$http_x_file_name"; } ... return 200 "OK"; } 

في الواقع ، استنادًا إلى قيمة المتغير http_x_context $ (رأس السياق X ) ، يمكننا تحديد إعادة توجيه Accel- response مع / x / captcha / check . هذا يعني أنه يمكنك استخدام هذه الآلية في أماكن أخرى إلى جانب تنزيل الملف.


مسح ذاكرة التخزين المؤقت بسيط للغاية:


 location /x/captcha/purge { allow 127.0.0.1; deny all; proxy_cache_purge captcha "$http_x_cache_key"; add_header X-Accel-Redirect "/x/captcha/check/ok"; } 

بشكل عام ، هذا كل شيء ، في النهاية حصلنا على تهيئة nginx التالية:


 proxy_cache_path /cache/nginx/captcha levels=1:1 keys_zone=captcha:10m max_size=128m; server { ... location /download { proxy_set_header Host $host; proxy_set_header X-Context download; proxy_set_header X-File-Name $arg_filename; proxy_set_header X-Key $arg_key; proxy_set_header X-Code $arg_code; proxy_set_header X-Cache-Key $arg_cache_key; proxy_pass http://127.0.0.1/x/captcha/check; proxy_intercept_errors on; error_page 403 404 = /download/fail; } location /download/fail { internal; return 200 "FAIL DOWNLOAD"; } location /store/file { internal; add_header Content-Disposition "attachment; filename=\"$arg_filename\""; alias /spool/tmp/; try_files $arg_filename =404; } ... set $captcha_salt 'salt'; if ( $request_id ~* "(\w{4})$" ) { set $cache_key $1; } location /x/captcha/generate { proxy_cache captcha; proxy_cache_key "$captcha_salt:$cache_key"; proxy_cache_valid 200 365d; proxy_cache_valid any 0s; proxy_set_header Host "captcha.service.domain.my"; proxy_pass http://captcha_upstream/?cache_key=$cache_key; } location /x/captcha/salt { allow {{ captcha backend IPs }}; deny all; return 200 "$captcha_salt"; } location /x/captcha/check { allow 127.0.0.1; deny all; secure_link_md5 "$captcha_salt$http_x_code$http_x_cache_key"; secure_link $http_x_key; if ($secure_link = "") { return 403 "FAIL CHECK CODE"; } proxy_set_header Host $host; proxy_pass http://127.0.0.1/x/captcha/purge; } location /x/captcha/check/ok { internal; if ( $http_x_context = 'download' ) { add_header X-Accel-Redirect "/store/file?filename=$http_x_file_name"; } ... return 200 "OK"; } location /x/captcha/purge { allow 127.0.0.1; deny all; proxy_cache_purge captcha "$http_x_cache_key"; add_header X-Accel-Redirect "/x/captcha/check/ok"; } } 

ما يجب الانتباه إليه:

  • Accel-Redirect يعمل فقط عندما تكون حالة الاستجابة 2XX. صحيح ، للأسف ، لم يكتب أي شيء عن هذا في أي مكان ، ولا يتفق أتباع nginx ؛
  • إغلاق المواقع الخاصة إما السماح 127.0.0.1 ؛ حرمان الجميع إما داخلي ؛ ، اعتمادًا على ما إذا كنا نصل إلى هذا الموقع من خلال proxy_pass أو من خلال Accel-Redirect ؛
  • يتم تمييز جميع المواقع المرتبطة بـ captcha في / x / capcha / ... بحيث يكون من الممكن تشكيل خدمة microservice ؛

من أجل الوضوح ، قمت أيضًا برسم مخطط للعمل:


صورة

ملخص


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


  • proxy_cache.
  • أكسل-إعادة توجيه.
  • error_page.
  • secure_link.
  • cache_purge.

الباقي هو البناء الصحيح للسلاسل المنطقية.


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

Source: https://habr.com/ru/post/ar485804/


All Articles