34. تمرين عملي: الإصدار 14
يتم الحصول على المجلد [http-servers/09] للإصدار 14 عن طريق نسخ المجلد [http-servers/08] من الإصدار 13.
34.1. مقدمة
CSRF (تزوير الطلبات عبر المواقع) هي تقنية لاختطاف الجلسة. يتم شرحها على النحو التالي في ويكيبيديا (https://fr.wikipedia.org/wiki/Cross-site_request_forgery):
- تمكنت مالوري من العثور على الرابط الذي يسمح لها بحذف الرسالة المعنية.
- ترسل مالوري رسالة إلى أليس تحتوي على صورة زائفة لعرضها (وهي في الواقع برنامج نصي). عنوان URL للصورة هو الرابط إلى البرنامج النصي الذي يحذف الرسالة المطلوبة.
- يجب أن يكون لدى أليس جلسة عمل مفتوحة في متصفحها للموقع الذي تستهدفه مالوري. هذا شرط أساسي لنجاح الهجوم بصمت دون إثارة طلب مصادقة من شأنه تنبيه أليس. يجب أن تتمتع هذه الجلسة بالأذونات اللازمة لتنفيذ طلب مالوري التدميري. ليس من الضروري أن تكون علامة تبويب المتصفح مفتوحة على الموقع المستهدف، ولا حتى أن يكون المتصفح قيد التشغيل. يكفي أن تكون الجلسة نشطة.
- تقرأ أليس رسالة مالوري؛ ويستخدم متصفحها الجلسة المفتوحة لأليس ولا يطلب مصادقة تفاعلية. ويحاول استرداد محتوى الصورة. وبذلك، يقوم المتصفح بتشغيل الرابط وحذف الرسالة، واسترداد صفحة ويب نصية كمحتوى للصورة. ونظرًا لأنه لا يتعرف على نوع الصورة المرتبطة، فإنه لا يعرض صورة، ولا تدرك أليس أن مالوري قد جعلتها للتو تحذف رسالة رغماً عنها.
حتى مع هذا الشرح، تظل تقنية CSRF صعبة الفهم. دعونا نرسم مخططًا توضيحيًا:

- في [1-2]، تتواصل أليس مع المنتدى (الموقع أ). يحافظ هذا المنتدى على جلسة لكل مستخدم. يخزن متصفح أليس ملف تعريف الارتباط الخاص بهذه الجلسة محليًا ويعيده في كل مرة يرسل فيها طلبًا جديدًا إلى الموقع أ؛
- في [3]، ترسل مالوري رسالة إلى أليس. تقرأ أليس الرسالة في متصفحها. الرسالة بتنسيق HTML وتحتوي على رابط لصورة على الموقع ب. في الواقع، هذا الرابط هو رابط لبرنامج نصي JavaScript يتم تشغيله بمجرد وصوله إلى متصفح أليس؛
- ثم يقوم نص JavaScript هذا بإرسال طلب إلى الموقع A. يرسل متصفح أليس الطلب تلقائيًا مع ملف تعريف الارتباط الخاص بالجلسة المخزن محليًا. وهنا يحدث الهجوم: نجحت مالوري في الوصول إلى الموقع A باستخدام بيانات اعتماد جلسة أليس. من هذه النقطة فصاعدًا، وبغض النظر عما يحدث، يكون الهجوم قد تم؛
لمواجهة هذا النوع من الهجمات، يمكن للموقع أ اتباع الخطوات التالية:
- مع كل تبادل [1-2] مع أليس، يرسل الموقع A مفتاحًا، يُشار إليه فيما بعد باسم رمز CSRF، والذي يجب على أليس إرجاعه في طلبها التالي. وبالتالي، مع كل طلب، يجب على أليس إرسال معلومتين:
- ملف تعريف ارتباط الجلسة؛
- رمز CSRF الذي تم استلامه في الرد على طلبها الأخير إلى الموقع A؛
وهنا تكمن الحماية: في حين أن المتصفح يرسل ملف تعريف الارتباط للجلسة تلقائيًا إلى الموقع A، فإنه لا يفعل ذلك مع رمز CSRF. ولهذا السبب، سيتم رفض التبادل 6-7 الذي يقوم به البرنامج النصي للهجوم لأن الطلب 6 لن يكون قد أرسل رمز CSRF؛
يمكن للموقع A إرسال رمز CSRF إلى أليس بطرق مختلفة لتطبيق HTML:
- يمكنه إرسال صفحة HTML مع كل طلب حيث تحتوي جميع الروابط على رمز CSRF، على سبيل المثال [http://siteA/chemin/csrf_token]. عندما تنقر أليس على أحد هذه الروابط أثناء الطلب التالي، سيقوم الموقع A ببساطة باسترداد رمز CSRF من عنوان URL للطلب والتحقق من صحته. هذا ما سيتم القيام به هنا؛
- بالنسبة لصفحات HTML التي تحتوي على نموذج، يمكنه إرسال النموذج مع حقل مخفي [input type='hidden'] يحتوي على رمز CSRF. سيتم إرساله تلقائيًا مع النموذج عندما ترسل أليس الصفحة. سيسترد الموقع A رمز CSRF من نص الطلب؛
- هناك تقنيات أخرى ممكنة؛
34.2. التكوين

نضيف قيمتين منطقيتين إلى تكوين [parameters] للتطبيق:
- [with_redissession]: عند تعيينه على True، يستخدم التطبيق جلسة Redis. وعند تعيينه على False، يستخدم التطبيق جلسة Flask قياسية؛
- [with_csrftoken]: عند تعيينها إلى True، تحتوي عناوين URL الخاصة بالتطبيق على رمز CSRF؛
# durée pause thread en secondes
"sleep_time": 0,
# serveur Redis
"with_redissession": True,
"redis": {
"host": "127.0.0.1",
"port": 6379
},
# token csrf
"with_csrftoken": False,
34.3. تنفيذ CSRF
سنضمن أنه عند:
config['parameters']['with_csrftoken']
مضبوطة على [True]، يقوم التطبيق بإرسال صفحات ويب إلى متصفح العميل تحتوي روابطها على رمز CSRF.
34.3.1. وحدة [flask_wtf]
سيتم تنفيذ رمز CSRF باستخدام الوحدة النمطية [flask_wtf]، التي نقوم بتثبيتها في محطة PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask_wtf
Collecting flask_wtf
…
34.3.2. عرض القوالب
نقدم فئة جديدة في النماذج:

فئة [AbstractBaseModelForView] هي كما يلي:
- السطر 9: تنفذ فئة [AbstractBaseModelForView] واجهة [InterfaceModelForView] التي تنفذها فئات النموذج؛
- الأسطر 11–13: لم يتم تنفيذ الأسلوب [get_model_for_view]؛
- الأسطر 15-20: تُنشئ الطريقة [get_csrftoken] رمز CSRF إذا تم تكوين التطبيق لاستخدامها. اعتمادًا على الموقف، تُرجع الدالة رمزًا يسبقه شرطة مائلة (/) أو سلسلة فارغة. تُنشئ الدالة [generate_csrf] دائمًا نفس القيمة لطلب عميل معين. تتضمن معالجة الطلب تنفيذ وظائف متنوعة. يؤدي استخدام [generate_csrf] في هذه الوظائف دائمًا إلى إنشاء نفس القيمة. ومع ذلك، يتم إنشاء رمز CSRF جديد عند الطلب التالي؛
ستتضمن جميع نماذج M للعرض V رمز CSRF على النحو التالي:
- تمتد كل فئة نموذج من الفئة الأساسية [AbstractBaseModelForView]؛
- السطر 8: يتم طلب رمز CSRF من الفئة الأصلية. نحصل إما على سلسلة فارغة أو سلسلة مثل [/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c]؛
34.3.3. طرق العرض
مما رأيناه للتو، ستحتوي جميع طرق العرض V على رمز CSRF في قالبها M. وبالتالي يمكنها استخدامه في الروابط التي تحتوي عليها. لنلقِ نظرة على بعض الأمثلة:
جزء المصادقة [v_authentification.html]
<!-- form HTML - post its values with the [authenticate-user] action -->
<form method="post" action="/authentifier-utilisateur{{modèle.csrf_token}}">
<!-- title -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
…
</form>
- السطر 2: بناءً على ما رأيناه للتو، سيكون عنوان URL الخاص بسمة [action] هو:
[/authentifier-utilisateur/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c]
أو
حسب ما إذا كان التطبيق قد تم تكوينه لاستخدام رموز CSRF؛
جزء حساب الضريبة [v-calcul-impot.html]
<!-- form HTML posted -->
<form method="post" action="/calculer-impot{{modèle.csrf_token}}">
<!-- 12-column message on blue background -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
…
</form>
قسم المحاكاة [v-liste-simulations.html]
{% if modèle.simulations is undefined or modèle.simulations|length==0 %}
<!-- message on blue background -->
<div class="alert alert-primary" role="alert">
<h4>Votre liste de simulations est vide</h4>
</div>
{% endif %}
{% if modèle.simulations is defined and modèle.simulations|length!=0 %}
<!-- message on blue background -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- simulation table -->
<table class="table table-sm table-hover table-striped">
…
<!-- table body (data displayed) -->
<tbody>
<!-- display each simulation by browsing the simulation table -->
{% for simulation in modèle.simulations %}
<!-- display a table row with 6 columns - <tr> tag -->
<!-- column 1: row header (simulation no.) - <th scope='row' tag -->
<!-- column 2: parameter value [married] - <td> tag -->
<!-- column 3: parameter value [children] - <td> tag -->
<!-- column 4: parameter value [salary] - <td> tag -->
<!-- column 5: [tax] parameter value - <td> tag -->
<!-- column 6: parameter value [surcôte] - <td> tag -->
<!-- column 7: parameter value [discount] - <td> tag -->
<!-- column 8: parameter value [reduction] - <td> tag -->
<!-- column 9: parameter value [rate] (of tax) - <td> tag -->
<!-- column 10: link to delete simulation - <td> tag -->
<tr>
<th scope="row">{{simulation.id}}</th>
<td>{{simulation.marié}}</td>
<td>{{simulation.enfants}}</td>
<td>{{simulation.salaire}}</td>
<td>{{simulation.impôt}}</td>
<td>{{simulation.surcôte}}</td>
<td>{{simulation.décôte}}</td>
<td>{{simulation.réduction}}</td>
<td>{{simulation.taux}}</td>
<td><a href="/supprimer-simulation/{{simulation.id}}{{modèle.csrf_token}}">Supprimer</a></td>
</tr>
{% endfor %}
</tr>
</tbody>
</table>
{% endif %}
مقتطف القائمة [v-menu.html]
<!-- bootstrap menu -->
<nav class="nav flex-column">
<!-- display a list of links HTML -->
{% for optionMenu in modèle.optionsMenu %}
<a class="nav-link" href="{{optionMenu.url}}{{modèle.csrf_token}}">{{optionMenu.text}}</a>
{% endfor %}
</nav>
34.3.4. المسارات
يوجد الآن نوعان من المسارات، اعتمادًا على ما إذا كانت تستخدم رمز CSRF أم لا:

- [routes_without_csrftoken] هي مسارات بدون رمز CSRF. هذه هي المسارات من الإصدار السابق؛
- [routes_with_csrftoken] هي مسارات تحتوي على رمز CSRF.
في [routes_with_csrftoken]، تحتوي المسارات الآن على معلمة إضافية، وهي رمز CSRF:
تحتوي جميع المسارات الآن على رمز CSRF في معلماتها، بما في ذلك مسار [/init-session]. وهذا يعني أنه لا يمكن للعميل تشغيل التطبيق عن طريق كتابة عنوان URL [/init-session/html] مباشرةً لأن رمز CSRF سيكون مفقودًا. يجب عليه الآن المرور عبر عنوان URL [/] في الأسطر 7–10.
يتم تحديد المسارات في البرنامج النصي الرئيسي [main]:
- الأسطر 9–13: اختيار المسارات اعتمادًا على ما إذا كان التطبيق يستخدم رموز CSRF أم لا؛
34.3.5. [MainController]
لكل طلب، يجب على الخادم التحقق من وجود رمز CSRF. سنقوم بذلك في وحدة التحكم الرئيسية [MainController]، التي تتولى معالجة جميع الطلبات:
- السطر 20: استرداد رمز CSRF من عنوان URL لطلب النموذج [http://machine:port/path/action/param1/param2/…/csrf_token]. يكون رمز الجلسة دائمًا العنصر الأخير في عنوان URL؛
- السطر 23: يتم التحقق من صحة رمز CSRF الذي تم استرداده من عنوان URL بمقارنته برمز CSRF الخاص بالجلسة. إذا كان غير صالح، فإن الدالة [validate_csrf] ترمي استثناء [ValidationError] (السطر 27)؛
- السطر 41: يتم تضمين رمز CSRF في الاستجابة المرسلة إلى العميل. سيحتاج عملاء JSON و XML إليه. وذلك لأن هؤلاء العملاء لا يتلقون صفحات HTML تحتوي على رمز CSRF في الروابط الموجودة داخل الصفحات. وبالتالي، سيتلقونه في استجابة JSON أو XML المرسلة من الخادم؛
ملاحظة: لا تتحقق الدالة [validate_csrf] في السطر 23 من وجود تطابق تام. يتم تخزين رمز CSRF في الجلسة تحت المفتاح [csrf_token]. تشير الاختبارات إلى أن رمز CSRF يكون صالحًا إذا تم إنشاؤه أثناء الجلسة. وبالتالي، إذا قمت باستبدال رمز CSRF [xyz] يدويًا في عنوان URL المعروض في المتصفح — على سبيل المثال، (/lister-simulations/xyz) — برمز آخر [abc] تم استلامه مسبقًا أثناء إجراء سابق، فسيتم تنفيذ الإجراء [/lister-simulations] بنجاح؛
34.4. اختبارات باستخدام متصفح
أولاً:
- ابدأ تشغيل الخادم مع تعيين المعلمة [with_csrftoken] على [True]؛
- اطلب عنوان URL [http://localhost:5000] باستخدام متصفح؛

- في [1]، رمز CSRF؛
لنقم ببعض العمليات حتى نحصل على قائمة بالمحاكاة:

الآن، أدخل عنوان URL [http://localhost:5000/supprimer-simulation/1/x] يدويًا لحذف المحاكاة التي تحمل الرقم التعريفي id=1. ندخل رمز CSRF غير صحيح عن قصد لنرى ما سيحدث. استجابة الخادم هي كما يلي:

ملاحظة 1: ليس من المؤكد أن الطريقة المستخدمة هنا كافية دائمًا لمواجهة هجمات CSRF. لنعد إلى مخطط الهجوم:

إذا كان البرنامج النصي JavaScript الذي تم تنزيله في [5] قادرًا على قراءة سجل المتصفح الذي تستخدمه أليس، فسيكون قادرًا على استرداد عناوين URL التي نفذها المتصفح، مثل [/target/csrf_token]. يمكنه بعد ذلك استرداد رمز الجلسة [csrf_token] وتنفيذ هجومه في [6-7]. ومع ذلك، لا يسمح المتصفح بالوصول إلا إلى سجل نافذة المتصفح التي يعمل فيها البرنامج النصي. لذلك، إذا لم تستخدم أليس نفس النافذة للتفاعل مع الموقع A [1-2] وقراءة رسالة مالوري [3]، فلن يكون هجوم CSRF ممكنًا.
34.5. عملاء وحدة التحكم
هناك طريقة أخرى لاختبار الإصدار 14 من التطبيق وهي إعادة استخدام الاختبارات من الإصدار 12 وتكييفها مع الخادم الجديد.

يتم إنشاء المجلد [impots/http-clients/09] في البداية عن طريق نسخ المجلد [impots/http-clients/07]. ثم يتم تعديله.
لنعد إلى المسارات التي تبدأ الجلسة:
لا تصلح أي من هذه المسارات لتهيئة جلسة JSON أو XML:
- الأسطر 2–5: المسار [/] يقوم بتهيئة جلسة عمل HTML؛
- الأسطر 8–11: المسار [/init-session] يتطلب رمز CSRF لا نعرفه؛
قررنا إضافة مسار جديد إلى الخادم:
- السطر 2: المسار الجديد. لا يتوقع رمز CSRF. وبذلك نكون قد عدنا إلى مسار [/init-session] من الإصدار السابق؛
- السطران 4-5: نقوم بإعادة توجيه العميل (JSON، XML، HTML) إلى المسار [/init-session]، الذي يتضمن رمز CSRF في معلماته؛
يمكنك اختبار هذا المسار الجديد في متصفح:

استجابة الخادم (التي تم تكوينها باستخدام [with_csrftoken=True]) هي كما يلي:

- في [1]، تمت إعادة توجيه الخادم إلى مسار [/init-session] مع وجود رمز CSRF في عنوان URL؛
- في [2]، يوجد رمز CSRF في قاموس JSON المرسل من الخادم، مرتبطًا بالمفتاح [csrf_token]؛
لنعد إلى كود العميل:

نقوم بتعديل التكوين [config] على النحو التالي:
config.update({
# fichier des contribuables
"taxpayersFilename": f"{script_dir}/../data/input/taxpayersdata.txt",
# fichier des résultats
"resultsFilename": f"{script_dir}/../data/output/résultats.json",
# fichier des erreurs
"errorsFilename": f"{script_dir}/../data/output/errors.txt",
# fichier de logs
"logsFilename": f"{script_dir}/../data/logs/logs.txt",
# le serveur de calcul de l'impôt
"server": {
"urlServer": "http://127.0.0.1:5000",
"user": {
"login": "admin",
"password": "admin"
},
"url_services": {
"calculate-tax": "/calculer-impot",
"get-admindata": "/get-admindata",
"calculate-tax-in-bulk-mode": "/calculer-impots",
"init-session": "/init-session-without-csrftoken",
"end-session": "/fin-session",
"authenticate-user": "/authentifier-utilisateur",
"get-simulations": "/lister-simulations",
"delete-simulation": "/supprimer-simulation",
}
},
# mode debug
"debug": True,
# csrf_token
"with_csrftoken": True,
}
)
…
# route init-session
url_services = config['server']['url_services']
if config['with_csrftoken']:
url_services['init-session'] = '/init-session-without-csrftoken'
else:
url_services['init-session'] = '/init-session'
- السطر 31: سيشير متغير منطقي إلى العميل ما إذا كان الخادم الذي يتصل به يعمل مع رموز CSRF أم لا؛
- الأسطر 37–40: يتم تعيين عنوان URL للخدمة الخاص بعملية [init-session]:
- إذا كان الخادم يستخدم رموز CSRF، فإن عنوان URL للخدمة هو [/init-session-without-csrftoken]؛
- وإلا، فإن عنوان URL للخدمة هو [/init-session]؛
تم إدخال المسار [/init-session-without-csrftoken]. وهو يسمح لعميل JSON/XML ببدء جلسة مع الخادم دون الحاجة إلى رمز CSRF. سيجد العميل هذا الرمز في استجابة الخادم.
ثم نقوم بتعديل فئة [ImpôtsDaoWithHttpSession] التي تُنفِّذ طبقة [dao] الخاصة بالعميل:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | |
- الأسطر 38–92: تتم معالجة رمز CSRF بشكل أساسي داخل طريقة [get_response]؛
- السطر 60: النقطة الأساسية هي المعلمة [allow_redirects=True]. هذه هي قيمتها الافتراضية، لكننا أردنا إبرازها؛
عند التواجد في وضع [with_csrftoken=True]:
- يبدأ العملاء تفاعلهم مع الخادم باستدعاء المسار [/init-session_without_csftoken/type_response]؛
- يستجيب الخادم لهذا الطلب بإعادة توجيه إلى المسار [/init-session/type_response/csrf_token]؛
- بسبب المعلمة [allow_redirects=True]، سيتبع هذه الإعادة التوجيه [طلبات] من العميل؛
- سيتم العثور على رمز CSRF في النتيجة المستردة في السطرين 72 و 74 المرتبطين بالمفتاح [csrf_token]؛
عند التواجد في وضع [with_csrftoken=False]:
- (تابع)
- يبدأ العملاء تفاعلهم مع الخادم باستدعاء المسار [/init-session/type_response]؛
- يستجيب الخادم لهذا الطلب بإعادة توجيه إلى المسار [/init-session/type_response]؛
- بسبب المعلمة [allow_redirects=True]، سيتبع هذه الإعادة التوجيه [طلبات] من العميل؛
- لا يوجد رمز CSRF لاسترداده في السطور 81-82. وبالتالي تظل الخاصية [self.__csrf_token] None (السطر 36)؛
- السطران 51–52: بالنسبة لجميع الطلبات اللاحقة، يتم إضافة رمز CSRF، إن وجد، إلى المسار الأولي؛
- السطران 81–82: يتم تخزين الرمز الجديد الذي تم إنشاؤه بواسطة الخادم لكل طلب عميل جديد محليًا ليتم إرجاعه في السطر 52 مع الطلب التالي؛
بالإضافة إلى ذلك، تتغير طريقة [init_session] قليلاً:
من المهم أن نتذكر هنا أننا أنشأنا مسارًا [/init-session-without-csrftoken/<response-type>] لبدء الحوار بين العميل والخادم بدون رمز CSRF. ومع ذلك، فقد رأينا أن طريقة [get_response] التي تم استدعاؤها في السطر 12 من الكود تضيف بشكل منهجي رمز CSRF المخزن في [self.__csrf_token] إلى نهاية عنوان URL للخدمة. ولهذا السبب، في السطر 6 من الكود، نقوم بإزالة رمز CSRF هذا إذا كان موجودًا.
هذا كل شيء. للاختبار، سنقوم بتشغيل:
- the console clients [main, main2, main3];
- فئات الاختبار [Test1HttpClientDaoWithSession] و[Test2HttpClientDaoWithSession]؛
عن طريق تعيين معلمة التكوين [with_csrftoken] على التوالي إلى True ثم False.

فيما يلي مثال على السجلات التي تم الحصول عليها عند تشغيل عميل [main json] مع [with_csrftoken=True]:
2020-08-08 16:33:23.317903, MainThread : début du calcul de l'impôt des contribuables
2020-08-08 16:33:23.317903, Thread-1 : début du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.317903, Thread-2 : début du calcul de l'impôt des 2 contribuables
2020-08-08 16:33:23.317903, Thread-3 : début du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.317903, Thread-4 : début du calcul de l'impôt des 1 contribuables
2020-08-08 16:33:23.379221, Thread-2 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.381073, Thread-4 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.386982, Thread-3 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.390269, Thread-1 : {"action": "init-session", "état": 700, "réponse": ["session démarrée avec le type de réponse json"], "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.413206, Thread-2 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.422877, Thread-2 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "non", "enfants": 3, "salaire": 100000, "impôt": 16782, "surcôte": 7176, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 3, "salaire": 100000, "impôt": 9200, "surcôte": 2180, "taux": 0.3, "décôte": 0, "réduction": 0, "id": 2}], "csrf_token": "ImFiZmZkYjZmMzFkZDc2YWRjNWYwOGM0NTBmMGM4ODJjYzViOWI4NGEi.Xy63sw.H5L0--yWsvfaWvggrGw78z5VnN0"}
2020-08-08 16:33:23.428622, Thread-4 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.429127, Thread-3 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.429127, Thread-1 : {"action": "authentifier-utilisateur", "état": 200, "réponse": "Authentification réussie", "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.429127, Thread-2 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "IjU1YjlmZDA0OWRhNTJlODFmYjgyYjlhM2ExYWNhZmUzNTk2NjA5NGIi.Xy63sw.nyNSvkcG6iG0oIMBjtYPo8ySgdw"}
2020-08-08 16:33:23.438519, Thread-2 : fin du calcul de l'impôt des 2 contribuables
2020-08-08 16:33:23.443033, Thread-4 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 3, "salaire": 200000, "impôt": 42842, "surcôte": 17283, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 1}], "csrf_token": "ImY5YzQyMjlkYzcyYmM4YmZiMGI0NWY5MjE4MzIzNDExZjc0MGQ3MWQi.Xy63sw.q6olg7IP_g2ro_RBFRCX1BX90g8"}
2020-08-08 16:33:23.446510, Thread-3 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 5, "salaire": 100000, "impôt": 4230, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "non", "enfants": 0, "salaire": 100000, "impôt": 22986, "surcôte": 0, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 2}, {"marié": "oui", "enfants": 2, "salaire": 30000, "impôt": 0, "surcôte": 0, "taux": 0.0, "décôte": 0, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 0, "salaire": 200000, "impôt": 64210, "surcôte": 7498, "taux": 0.45, "décôte": 0, "réduction": 0, "id": 4}], "csrf_token": "IjkxZGNlN2YyMmUxMjQ0M2Y0MTdjNDQ4ZmQ1MDMxZjkwNjBhNzAzZjMi.Xy63sw.-6buL11No3UJBlElpW4tX4B-lp0"}
2020-08-08 16:33:23.453477, Thread-1 : {"action": "calculer-impots", "état": 1500, "réponse": [{"marié": "oui", "enfants": 2, "salaire": 55555, "impôt": 2814, "surcôte": 0, "taux": 0.14, "décôte": 0, "réduction": 0, "id": 1}, {"marié": "oui", "enfants": 2, "salaire": 50000, "impôt": 1384, "surcôte": 0, "taux": 0.14, "décôte": 384, "réduction": 347, "id": 2}, {"marié": "oui", "enfants": 3, "salaire": 50000, "impôt": 0, "surcôte": 0, "taux": 0.14, "décôte": 720, "réduction": 0, "id": 3}, {"marié": "non", "enfants": 2, "salaire": 100000, "impôt": 19884, "surcôte": 4480, "taux": 0.41, "décôte": 0, "réduction": 0, "id": 4}], "csrf_token": "IjIxNmU4MDQyZDFmZmIyZDlmZjE4MzNlNDUzYzFjMGYxMWYxYzEwNGYi.Xy63sw.fgs6Cm2owsJf4NjTm7gKrVESabI"}
2020-08-08 16:33:23.457912, Thread-4 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "IjQ0ZDQxODgzN2M5NjRiYWI0NjA2MTk5YWFkNGFhMzY1M2IxNWMyNDIi.Xy63sw.mOa5MKXvJ-EXf_qEok-OqC5j_mg"}
2020-08-08 16:33:23.458442, Thread-4 : fin du calcul de l'impôt des 1 contribuables
2020-08-08 16:33:23.459045, Thread-3 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "ImQ0NDZlYmViYjY1ZDUxYzJhMTNmM2JiZTRkMjBjZGJkYzE0OGVkYzMi.Xy63sw.fviTJz4zFDqVLlVlkrosT_JRPww"}
2020-08-08 16:33:23.459700, Thread-3 : fin du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.460492, Thread-1 : {"action": "fin-session", "état": 400, "réponse": "session réinitialisée", "csrf_token": "Ijg3MjQ1NGUyYTUyOGEyNTdmZmNmYWZkMmU2OTgyMzUwNjI1YTlhZjIi.Xy63sw.I0xBl9Q8DzsuXPSgOdeARc_VKBA"}
2020-08-08 16:33:23.460492, Thread-1 : fin du calcul de l'impôt des 4 contribuables
2020-08-08 16:33:23.460492, MainThread : fin du calcul de l'impôt des contribuables
إذا نظرنا إلى رموز CSRF التي تم استلامها بالتتابع، نرى أنها جميعها مختلفة.