34. Praktische Übung: Version 14
Der Ordner [http-servers/09] für Version 14 wird durch Kopieren des Ordners [http-servers/08] aus Version 13 erstellt.
34.1. Einführung
CSRF (Cross-Site Request Forgery) ist eine Technik zum Session-Hijacking. Auf Wikipedia (https://fr.wikipedia.org/wiki/Cross-site_request_forgery) wird dies wie folgt erklärt:
- Malorie gelingt es, den Link zu finden, über den sie die betreffende Nachricht löschen kann.
- Malorie sendet Alice eine Nachricht, die ein anzuzeigendes Pseudo-Bild enthält (das in Wirklichkeit ein Skript ist). Die URL des Bildes ist der Link zu dem Skript, das die gewünschte Nachricht löscht.
- Alice muss in ihrem Browser eine offene Sitzung für die Website haben, auf die Malorie abzielt. Dies ist eine Voraussetzung dafür, dass der Angriff unbemerkt gelingt, ohne eine Authentifizierungsanfrage auszulösen, die Alice alarmieren würde. Diese Sitzung muss über die erforderlichen Berechtigungen verfügen, um Malories zerstörerische Anfrage auszuführen. Es ist nicht notwendig, dass ein Browser-Tab auf der Zielseite geöffnet ist, noch muss der Browser überhaupt laufen. Es reicht aus, dass die Sitzung aktiv ist.
- Alice liest Malories Nachricht; ihr Browser nutzt Alices offene Sitzung und fordert keine interaktive Authentifizierung an. Er versucht, den Inhalt des Bildes abzurufen. Dabei löst der Browser den Link aus und löscht die Nachricht, wobei er eine textbasierte Webseite als Bildinhalt abruft. Da er den zugehörigen Bildtyp nicht erkennt, zeigt er kein Bild an, und Alice merkt nicht, dass Malorie sie gerade dazu gebracht hat, eine Nachricht gegen ihren Willen zu löschen.
Selbst so erklärt ist die CSRF-Technik schwer zu verstehen. Zeichnen wir ein Diagramm:

- In [1-2] kommuniziert Alice mit dem Forum (Website A). Dieses Forum unterhält für jeden Benutzer eine Sitzung. Alices Browser speichert dieses Sitzungs-Cookie lokal und sendet es jedes Mal zurück, wenn er eine neue Anfrage an Website A stellt;
- In [3] sendet Malorie eine Nachricht an Alice. Alice liest sie in ihrem Browser. Die Nachricht ist im HTML-Format und enthält einen Link zu einem Bild auf Website B. Tatsächlich ist dieser Link ein Link zu einem JavaScript-Skript, das ausgeführt wird, sobald es Alices Browser erreicht;
- Dieses JavaScript-Skript sendet daraufhin eine Anfrage an Website A. Alices Browser sendet die Anfrage automatisch zusammen mit dem lokal gespeicherten Sitzungscookie. Hier findet der Angriff statt: Malorie hat mit Alices Sitzungsdaten erfolgreich auf Website A zugegriffen. Ab diesem Zeitpunkt ist der Angriff, unabhängig davon, was noch geschieht, erfolgreich;
Um dieser Art von Angriff entgegenzuwirken, kann Website A wie folgt vorgehen:
- Bei jedem Austausch [1-2] mit Alice sendet Website A einen Schlüssel, im Folgenden als CSRF-Token bezeichnet, den Alice in ihrer nächsten Anfrage zurücksenden muss. Somit muss Alice bei jeder Anfrage zwei Informationen senden:
- das Sitzungs-Cookie;
- das CSRF-Token, das sie in der Antwort auf ihre letzte Anfrage an Website A erhalten hat;
Hierin liegt der Schutz: Während der Browser das Sitzungscookie automatisch an Website A zurücksendet, tut er dies nicht für das CSRF-Token. Aus diesem Grund wird der vom Angriffsskript durchgeführte Austausch 6-7 abgelehnt, da in Anfrage 6 das CSRF-Token nicht gesendet wurde;
Website A kann Alice das CSRF-Token auf verschiedene Weise für eine HTML-Anwendung senden:
- Sie kann bei jeder Anfrage eine HTML-Seite senden, auf der alle Links das CSRF-Token enthalten, zum Beispiel [http://siteA/chemin/csrf_token]. Wenn Alice bei der nächsten Anfrage auf einen dieser Links klickt, ruft Website A einfach das CSRF-Token aus der Anfrage-URL ab und überprüft dessen Gültigkeit. Genau das wird hier geschehen;
- Bei HTML-Seiten, die ein Formular enthalten, kann sie das Formular mit einem versteckten Feld [input type='hidden'] senden, das das CSRF-Token enthält. Dieses wird dann automatisch mit dem Formular übermittelt, wenn Alice die Seite absendet. Website A ruft das CSRF-Token aus dem Hauptteil der Anfrage ab;
- Es sind auch andere Techniken möglich;
34.2. Konfiguration

Wir fügen der [parameters]-Konfiguration der Anwendung zwei boolesche Werte hinzu:
- [with_redissession]: Wenn auf „True“ gesetzt, verwendet die Anwendung eine Redis-Sitzung. Wenn auf „False“ gesetzt, verwendet die Anwendung eine Standard-Flask-Sitzung;
- [with_csrftoken]: Wenn auf „True“ gesetzt, enthalten die URLs der Anwendung ein CSRF-Token;
# 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-Implementierung
Wir stellen sicher, dass, wenn:
config['parameters']['with_csrftoken']
auf [True] gesetzt ist, die Anwendung Webseiten an den Browser des Clients sendet, deren Links ein CSRF-Token enthalten.
34.3.1. Das Modul [flask_wtf]
Das CSRF-Token wird mithilfe des Moduls [flask_wtf] implementiert, das wir in einem PyCharm-Terminal installieren:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask_wtf
Collecting flask_wtf
…
34.3.2. Ansichtsvorlagen
Wir führen eine neue Klasse in den Modellen ein:

Die Klasse [AbstractBaseModelForView] sieht wie folgt aus:
- Zeile 9: Die Klasse [AbstractBaseModelForView] implementiert die Schnittstelle [InterfaceModelForView], die von den Modellklassen implementiert wird;
- Zeilen 11–13: Die Methode [get_model_for_view] ist nicht implementiert;
- Zeilen 15–20: Die Methode [get_csrftoken] generiert das CSRF-Token, sofern die Anwendung für dessen Verwendung konfiguriert wurde. Je nach Situation gibt die Funktion ein Token zurück, dem ein Schrägstrich (/) vorangestellt ist, oder eine leere Zeichenkette. Die Funktion [generate_csrf] generiert für eine bestimmte Client-Anfrage immer denselben Wert. Die Verarbeitung einer Anfrage umfasst die Ausführung verschiedener Funktionen. Die Verwendung von [generate_csrf] in diesen Funktionen erzeugt immer denselben Wert. Bei der nächsten Anfrage wird jedoch ein neues CSRF-Token generiert;
Alle M-Modelle für die Ansicht V enthalten das CSRF-Token wie folgt:
- Jede Modellklasse erweitert die Basisklasse [AbstractBaseModelForView];
- Zeile 8: Das CSRF-Token wird von der übergeordneten Klasse angefordert. Wir erhalten entweder eine leere Zeichenfolge oder eine Zeichenfolge wie [/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c];
34.3.3. Die Ansichten
Nach dem, was wir gerade gesehen haben, verfügen alle Ansichten V über das CSRF-Token in ihrer Vorlage M. Sie können es daher in den darin enthaltenen Links verwenden. Sehen wir uns einige Beispiele an:
Das Authentifizierungsfragment [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>
- Zeile 2: Basierend auf dem, was wir gerade gesehen haben, lautet die URL für das [action]-Attribut:
[/authentifier-utilisateur/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c]
oder
je nachdem, ob die Anwendung für die Verwendung von CSRF-Tokens konfiguriert wurde;
Das Fragment zur Steuerberechnung [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>
Der Abschnitt „Simulationen“ [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 %}
Das Menü-Snippet [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. Routen
Es gibt nun zwei Arten von Routen, je nachdem, ob sie ein CSRF-Token verwenden oder nicht:

- [routes_without_csrftoken] sind Routen ohne CSRF-Token. Dies sind die Routen aus der vorherigen Version;
- [routes_with_csrftoken] sind Routen mit einem CSRF-Token.
In [routes_with_csrftoken] verfügen Routen nun über einen zusätzlichen Parameter, das CSRF-Token:
Alle Routen enthalten nun das CSRF-Token in ihren Parametern, einschließlich der Route [/init-session]. Das bedeutet, dass der Client die Anwendung nicht durch direkte Eingabe der URL [/init-session/html] starten kann, da das CSRF-Token fehlen würde. Er muss nun die URL [/] in den Zeilen 7–10 durchlaufen.
Die Routen werden im Hauptskript [main] ausgewählt:
- Zeilen 9–13: Auswahl der Routen je nachdem, ob die Anwendung CSRF-Token verwendet;
34.3.5. Der [MainController]
Bei jeder Anfrage muss der Server das Vorhandensein des CSRF-Tokens überprüfen. Dies erfolgt im Hauptcontroller [MainController], der alle Anfragen verarbeitet:
- Zeile 20: Abrufen des CSRF-Tokens aus der Anfrage-URL des Formulars [http://machine:port/path/action/param1/param2/…/csrf_token]. Das Session-Token ist immer das letzte Element der URL;
- Zeile 23: Die Gültigkeit des aus der URL abgerufenen CSRF-Tokens wird mit dem CSRF-Token der Sitzung abgeglichen. Ist es ungültig, löst die Funktion [validate_csrf] eine [ValidationError]-Ausnahme aus (Zeile 27);
- Zeile 41: Das CSRF-Token wird in die an den Client gesendete Antwort aufgenommen. JSON- und XML-Clients benötigen es. Der Grund dafür ist, dass diese Clients keine HTML-Seiten mit dem CSRF-Token in den auf den Seiten enthaltenen Links erhalten. Sie erhalten es daher in der vom Server gesendeten JSON- oder XML-Antwort;
Hinweis: Die Funktion [validate_csrf] in Zeile 23 prüft nicht auf eine exakte Übereinstimmung. Das CSRF-Token wird in der Sitzung unter dem Schlüssel [csrf_token] gespeichert. Tests deuten darauf hin, dass ein CSRF-Token gültig ist, wenn es während der Sitzung generiert wurde. Wenn Sie also das CSRF-Token [xyz] in der im Browser angezeigten URL – zum Beispiel (/lister-simulations/xyz) – manuell durch ein anderes Token [abc] ersetzen, das Sie zuvor bei einer früheren Aktion erhalten haben, wird die Aktion [/lister-simulations] erfolgreich ausgeführt;
34.4. Tests mit einem Browser
Erstens:
- Starten Sie den Server mit dem Parameter [with_csrftoken] auf [True] gesetzt;
- Rufen Sie die URL [http://localhost:5000] über einen Browser auf;

- in [1] das CSRF-Token;
Führen wir einige Operationen durch, bis wir eine Liste von Simulationen haben:

Geben Sie nun manuell die URL [http://localhost:5000/supprimer-simulation/1/x] ein, um die Simulation mit der ID=1 zu löschen. Wir geben absichtlich ein falsches CSRF-Token ein, um zu sehen, was passiert. Die Antwort des Servers lautet wie folgt:

Anmerkung 1: Es ist nicht sicher, dass die hier verwendete Methode immer ausreicht, um CSRF-Angriffen entgegenzuwirken. Kehren wir zum Angriffsdiagramm zurück:

Wenn das in [5] heruntergeladene JavaScript-Skript in der Lage ist, den von Alice verwendeten Browserverlauf auszulesen, kann es die vom Browser ausgeführten URLs abrufen, wie beispielsweise [/target/csrf_token]. Es kann dann das Sitzungstoken [csrf_token] abrufen und seinen Angriff in [6-7] ausführen. Der Browser erlaubt jedoch nur den Zugriff auf den Verlauf des Browserfensters, in dem das Skript ausgeführt wird. Wenn Alice also nicht dasselbe Fenster verwendet, um mit Website A zu interagieren [1-2] und Malories Nachricht zu lesen [3], ist der CSRF-Angriff nicht möglich.
34.5. Konsolen-Clients
Eine weitere Möglichkeit, Version 14 der Anwendung zu testen, besteht darin, die Tests aus Version 12 wiederzuverwenden und sie an den neuen Server anzupassen.

Der Ordner [impots/http-clients/09] wird zunächst durch Kopieren des Ordners [impots/http-clients/07] erstellt. Anschließend wird er angepasst.
Kommen wir zurück zu den Routen, die eine Sitzung initialisieren:
Keine dieser Routen eignet sich für die Initialisierung einer JSON- oder XML-Sitzung:
- Zeilen 2–5: Die Route [/] initialisiert eine HTML-Sitzung;
- Zeilen 8–11: Die Route [/init-session] erfordert ein CSRF-Token, das uns nicht bekannt ist;
Wir beschließen, dem Server eine neue Route hinzuzufügen:
- Zeile 2: die neue Route. Sie erwartet kein CSRF-Token. Wir sind somit zur Route [/init-session] aus der vorherigen Version zurückgekehrt;
- Zeilen 4–5: Wir leiten den Client (JSON, XML, HTML) zur Route [/init-session] weiter, die das CSRF-Token in ihren Parametern enthält;
Sie können diese neue Route in einem Browser testen:

Die Antwort des Servers (konfiguriert mit [with_csrftoken=True]) lautet wie folgt:

- In [1] wurde der Server zur Route [/init-session] umgeleitet, wobei das CSRF-Token in der URL enthalten war;
- in [2] befindet sich das CSRF-Token im vom Server gesendeten JSON-Dictionary, verknüpft mit dem Schlüssel [csrf_token];
Kehren wir zum Client-Code zurück:

Wir ändern die [config]-Konfiguration wie folgt:
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'
- Zeile 31: Ein boolescher Wert teilt dem Client mit, ob der Server, an den er sich wendet, mit CSRF-Tokens arbeitet oder nicht;
- Zeilen 37–40: Die Service-URL für die Aktion [init-session] wird festgelegt:
- Wenn der Server CSRF-Token verwendet, lautet die Service-URL [/init-session-without-csrftoken];
- ansonsten lautet die Service-URL [/init-session];
Die Route [/init-session-without-csrftoken] wurde eingeführt. Sie ermöglicht es einem JSON/XML-Client, eine Sitzung mit dem Server zu starten, ohne über ein CSRF-Token zu verfügen. Der Client findet dieses Token in der Antwort des Servers.
Anschließend passen wir die Klasse [ImpôtsDaoWithHttpSession] an, die die [dao]-Schicht des Kunden implementiert:

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 | |
- Zeilen 38–92: Die Verarbeitung des CSRF-Tokens erfolgt hauptsächlich innerhalb der Methode [get_response];
- Zeile 60: Der entscheidende Punkt ist der Parameter [allow_redirects=True]. Dies ist sein Standardwert, aber wir wollten ihn hervorheben;
Im Modus [with_csrftoken=True]:
- beginnen Clients ihre Interaktion mit dem Server durch Aufruf der Route [/init-session_without_csftoken/type_response];
- Der Server antwortet auf diese Anfrage mit einer Weiterleitung zur Route [/init-session/type_response/csrf_token];
- Aufgrund des Parameters [allow_redirects=True] wird diese Weiterleitung vom Client befolgt [requests];
- Das CSRF-Token befindet sich im abgerufenen Ergebnis in den Zeilen 72 und 74, zugeordnet dem Schlüssel [csrf_token];
Im Modus [with_csrftoken=False]:
- (Fortsetzung)
- beginnen die Clients ihre Interaktion mit dem Server durch Aufruf der Route [/init-session/type_response];
- Der Server antwortet auf diese Anfrage mit einer Weiterleitung zur Route [/init-session/type_response];
- aufgrund des Parameters [allow_redirects=True] wird diese Weiterleitung vom Client befolgt [requests];
- in den Zeilen 81–82 gibt es kein CSRF-Token abzurufen. Die Eigenschaft [self.__csrf_token] bleibt daher None (Zeile 36);
- Zeilen 51–52: Bei allen nachfolgenden Anfragen wird das CSRF-Token, sofern vorhanden, der ursprünglichen Route hinzugefügt;
- Zeilen 81–82: Das vom Server für jede neue Client-Anfrage generierte neue Token wird lokal gespeichert, um in Zeile 52 mit der nächsten Anfrage zurückgegeben zu werden;
Zudem ändert sich die Methode [init_session] geringfügig:
Es ist wichtig, sich hier daran zu erinnern, dass wir eine Route [/init-session-without-csrftoken/<response-type>] erstellt haben, um den Client-Server-Dialog ohne CSRF-Token zu initialisieren. Wir haben jedoch gesehen, dass die in Zeile 12 des Codes aufgerufene Methode [get_response] das in [self.__csrf_token] gespeicherte CSRF-Token systematisch an das Ende der Service-URL anhängt. Deshalb entfernen wir in Zeile 6 des Codes dieses CSRF-Token, falls es vorhanden ist.
Das war's. Zum Testen führen wir Folgendes aus:
- the console clients [main, main2, main3];
- die Testklassen [Test1HttpClientDaoWithSession] und [Test2HttpClientDaoWithSession];
indem der Konfigurationsparameter [with_csrftoken] nacheinander auf „True“ und dann auf „False“ gesetzt wird.

Hier ist ein Beispiel für die Protokolle, die bei der Ausführung des [main json]-Clients mit [with_csrftoken=True] erhalten werden:
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
Wenn wir uns die nacheinander empfangenen CSRF-Token ansehen, stellen wir fest, dass sie alle unterschiedlich sind.