34. Esercizio pratico: versione 14
La cartella [http-servers/09] per la versione 14 si ottiene copiando la cartella [http-servers/08] dalla versione 13.
34.1. Introduzione
Il CSRF (Cross-Site Request Forgery) è una tecnica di dirottamento della sessione. Su Wikipedia (https://fr.wikipedia.org/wiki/Cross-site_request_forgery) viene spiegato come segue:
- Malorie riesce a trovare il link che le permette di cancellare il messaggio in questione.
- Malorie invia un messaggio ad Alice contenente una pseudo-immagine da visualizzare (che in realtà è uno script). L'URL dell'immagine è il link allo script che elimina il messaggio desiderato.
- Alice deve avere una sessione aperta nel suo browser per il sito che Malorie sta prendendo di mira. Questo è un prerequisito affinché l'attacco abbia successo in modo silenzioso senza innescare una richiesta di autenticazione che allertarebbe Alice. Questa sessione deve disporre delle autorizzazioni necessarie per eseguire la richiesta distruttiva di Malorie. Non è necessario che una scheda del browser sia aperta sul sito di destinazione, né che il browser sia in esecuzione. È sufficiente che la sessione sia attiva.
- Alice legge il messaggio di Malorie; il suo browser utilizza la sessione aperta di Alice e non richiede un'autenticazione interattiva. Tenta di recuperare il contenuto dell'immagine. Nel farlo, il browser attiva il link ed elimina il messaggio, recuperando una pagina web testuale come contenuto dell'immagine. Poiché non riconosce il tipo di immagine associato, non visualizza alcuna immagine e Alice non si rende conto che Malorie le ha appena fatto cancellare un messaggio contro la sua volontà.
Anche spiegata in questo modo, la tecnica CSRF è difficile da comprendere. Disegniamo un diagramma:

- In [1-2], Alice comunica con il forum (Sito A). Questo forum mantiene una sessione per ogni utente. Il browser di Alice memorizza localmente questo cookie di sessione e lo invia ogni volta che effettua una nuova richiesta al Sito A;
- In [3], Malorie invia un messaggio ad Alice. Alice lo legge nel suo browser. Il messaggio è in formato HTML e contiene un link a un'immagine sul Sito B. In realtà, questo link rimanda a uno script JavaScript che viene eseguito non appena raggiunge il browser di Alice;
- Questo script JavaScript invia quindi una richiesta al Sito A. Il browser di Alice invia automaticamente la richiesta insieme al cookie di sessione memorizzato localmente. È qui che avviene l'attacco: Malorie è riuscita ad accedere al Sito A utilizzando le credenziali di sessione di Alice. Da questo momento in poi, indipendentemente da ciò che accadrà, l'attacco ha avuto luogo;
Per contrastare questo tipo di attacco, il Sito A può procedere come segue:
- Ad ogni scambio [1-2] con Alice, il Sito A invia una chiave, di seguito denominata token CSRF, che Alice deve restituire nella sua richiesta successiva. Pertanto, ad ogni richiesta, Alice deve inviare due informazioni:
- il cookie di sessione;
- il token CSRF ricevuto nella risposta alla sua ultima richiesta al Sito A;
È qui che risiede la protezione: mentre il browser invia automaticamente il cookie di sessione al Sito A, non lo fa per il token CSRF. Per questo motivo, lo scambio 6-7 eseguito dallo script di attacco verrà rifiutato perché la richiesta 6 non avrà inviato il token CSRF;
Il sito A può inviare ad Alice il token CSRF in vari modi per un'applicazione HTML:
- Può inviare una pagina HTML con ogni richiesta in cui tutti i link contengono il token CSRF, ad esempio [http://siteA/chemin/csrf_token]. Quando Alice clicca su uno di questi link durante la richiesta successiva, il Sito A recupererà semplicemente il token CSRF dall'URL della richiesta e verificherà che sia valido. Questo è ciò che verrà fatto in questo caso;
- per le pagine HTML contenenti un modulo, può inviare il modulo con un campo nascosto [input type='hidden'] contenente il token CSRF. Questo verrà quindi inviato automaticamente con il modulo quando Alice invia la pagina. Il sito A recupererà il token CSRF dal corpo della richiesta;
- sono possibili altre tecniche;
34.2. Configurazione

Aggiungiamo due valori booleani alla configurazione [parameters] dell'applicazione:
- [with_redissession]: se impostato su True, l'applicazione utilizza una sessione Redis. Se impostato su False, l'applicazione utilizza una sessione Flask standard;
- [with_csrftoken]: se impostato su True, gli URL dell'applicazione contengono un token 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. Implementazione CSRF
Ci assicureremo che quando:
config['parameters']['with_csrftoken']
è impostato su [True], l'applicazione invii al browser del client pagine web i cui link contengano un token CSRF.
34.3.1. Il modulo [flask_wtf]
Il token CSRF verrà implementato utilizzando il modulo [flask_wtf], che installiamo in un terminale PyCharm:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\packages>pip install flask_wtf
Collecting flask_wtf
…
34.3.2. Visualizza modelli
Stiamo introducendo una nuova classe nei modelli:

La classe [AbstractBaseModelForView] è la seguente:
- riga 9: la classe [AbstractBaseModelForView] implementa l'interfaccia [InterfaceModelForView] implementata dalle classi modello;
- righe 11–13: il metodo [get_model_for_view] non è implementato;
- Righe 15–20: il metodo [get_csrftoken] genera il token CSRF se l'applicazione è stata configurata per utilizzarli. A seconda della situazione, la funzione restituisce un token preceduto da una barra (/) o una stringa vuota. La funzione [generate_csrf] genera sempre lo stesso valore per una data richiesta del client. L'elaborazione di una richiesta comporta l'esecuzione di varie funzioni. L'uso di [generate_csrf] in queste funzioni genera sempre lo stesso valore. Alla richiesta successiva, tuttavia, viene generato un nuovo token CSRF;
Tutti i modelli M per la vista V includeranno il token CSRF come segue:
- Ogni classe di modello estende la classe base [AbstractBaseModelForView];
- Riga 8: Il token CSRF viene richiesto alla classe padre. Otteniamo una stringa vuota o una stringa simile a [/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c];
34.3.3. Le viste
Da quanto abbiamo appena visto, tutte le viste V avranno il token CSRF nel loro modello M. Possono quindi utilizzarlo nei link che contengono. Vediamo alcuni esempi:
Il frammento di autenticazione [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>
- riga 2: in base a quanto appena visto, l'URL per l'attributo [action] sarà:
[/authentifier-utilisateur/Ijk4NjQ2ZDdjZjI0ZDJiYTVjZTZjYmFhZGNjMjE3Y2U5M2I3ODI0NzYi.Xy5Okg.n-kSR_nslkndfT7AFVy2UDtdb8c]
oppure
a seconda che l'applicazione sia stata configurata per utilizzare i token CSRF;
Il frammento di calcolo delle imposte [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>
La sezione delle simulazioni [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 %}
Il frammento di codice del menu [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. Percorsi
Ora ci sono due tipi di percorsi, a seconda che utilizzino o meno un token CSRF:

- [routes_without_csrftoken] sono percorsi senza token CSRF. Si tratta dei percorsi della versione precedente;
- [routes_with_csrftoken] sono percorsi con un token CSRF.
In [routes_with_csrftoken], i percorsi ora hanno un parametro aggiuntivo, il token CSRF:
Tutte le rotte ora hanno il token CSRF nei loro parametri, inclusa la rota [/init-session]. Ciò significa che il client non può avviare l'applicazione digitando direttamente l'URL [/init-session/html] perché mancherà il token CSRF. Ora deve passare attraverso l'URL [/] nelle righe 7–10.
I percorsi sono selezionati nello script principale [main]:
- righe 9–13: selezione dei percorsi a seconda che l'applicazione utilizzi token CSRF;
34.3.5. Il [MainController]
Per ogni richiesta, il server deve verificare la presenza del token CSRF. Lo faremo nel controller principale [MainController], che gestisce tutte le richieste:
- Riga 20: Recuperiamo il token CSRF dall'URL della richiesta del modulo [http://machine:port/path/action/param1/param2/…/csrf_token]. Il token di sessione è sempre l'ultimo elemento dell'URL;
- riga 23: la validità del token CSRF recuperato dall'URL viene verificata rispetto al token CSRF della sessione. Se non è valido, la funzione [validate_csrf] genera un'eccezione [ValidationError] (riga 27);
- riga 41: il token CSRF viene incluso nella risposta inviata al client. I client JSON e XML ne avranno bisogno. Questo perché tali client non ricevono pagine HTML con il token CSRF nei link contenuti nelle pagine. Lo riceveranno quindi nella risposta JSON o XML inviata dal server;
Nota: la funzione [validate_csrf] alla riga 23 non verifica la corrispondenza esatta. Il token CSRF è memorizzato nella sessione sotto la chiave [csrf_token]. I test sembrano indicare che un token CSRF è valido se è stato generato durante la sessione. Pertanto, se si sostituisce manualmente il token CSRF [xyz] nell'URL visualizzato nel browser — ad esempio, (/lister-simulations/xyz) — con un altro token [abc] ricevuto in precedenza durante un'azione precedente, l'azione [/lister-simulations] avrà esito positivo;
34.4. Test con un browser
Primo:
- avviare il server con il parametro [with_csrftoken] impostato su [True];
- richiedere l'URL [http://localhost:5000] utilizzando un browser;

- in [1], il token CSRF;
Eseguiamo alcune operazioni fino a ottenere un elenco di simulazioni:

Ora, inseriamo manualmente l'URL [http://localhost:5000/supprimer-simulation/1/x] per cancellare la simulazione con id=1. Inseriamo intenzionalmente un token CSRF errato per vedere cosa succede. La risposta del server è la seguente:

Nota 1: non è certo che il metodo qui utilizzato sia sempre sufficiente per contrastare gli attacchi CSRF. Torniamo al diagramma dell'attacco:

Se lo script JavaScript scaricato in [5] è in grado di leggere la cronologia del browser utilizzata da Alice, potrà recuperare gli URL eseguiti dal browser, come [/target/csrf_token]. Potrà quindi recuperare il token di sessione [csrf_token] e sferrare il suo attacco in [6-7]. Tuttavia, il browser consente l'accesso solo alla cronologia della finestra del browser in cui lo script è in esecuzione. Pertanto, se Alice non utilizza la stessa finestra per interagire con il Sito A [1-2] e leggere il messaggio di Malorie [3], l'attacco CSRF non sarà possibile.
34.5. Client console
Un altro modo per testare la versione 14 dell'applicazione è riutilizzare i test della versione 12 e adattarli al nuovo server.

La cartella [impots/http-clients/09] viene inizialmente creata copiando la cartella [impots/http-clients/07]. Viene poi modificata.
Torniamo alle routine che inizializzano una sessione:
Nessuno di questi percorsi è adatto per l'inizializzazione di una sessione JSON o XML:
- righe 2–5: il percorso [/] inizializza una sessione HTML;
- righe 8–11: il percorso [/init-session] richiede un token CSRF che non conosciamo;
Decidiamo di aggiungere un nuovo percorso al server:
- riga 2: il nuovo percorso. Non richiede un token CSRF. Siamo quindi tornati al percorso [/init-session] della versione precedente;
- righe 4-5: reindirizziamo il client (JSON, XML, HTML) al percorso [/init-session], che include il token CSRF nei suoi parametri;
È possibile testare questo nuovo percorso in un browser:

La risposta del server (configurata con [with_csrftoken=True]) è la seguente:

- in [1], il server è stato reindirizzato alla route [/init-session] con il token CSRF nell'URL;
- in [2], il token CSRF si trova nel dizionario JSON inviato dal server, associato alla chiave [csrf_token];
Torniamo al codice client:

Modifichiamo la configurazione [config] come segue:
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'
- riga 31: un valore booleano indicherà al client se il server a cui si sta rivolgendo utilizza token CSRF o meno;
- righe 37–40: viene impostato l'URL del servizio per l'azione [init-session]:
- se il server utilizza token CSRF, l'URL del servizio è [/init-session-without-csrftoken];
- altrimenti, l'URL del servizio è [/init-session];
È stato introdotto il percorso [/init-session-without-csrftoken]. Consente a un client JSON/XML di avviare una sessione con il server senza disporre di un token CSRF. Il client troverà questo token nella risposta del server.
Modifichiamo quindi la classe [ImpôtsDaoWithHttpSession] che implementa il livello [dao] del cliente:

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 | |
- righe 38–92: la gestione del token CSRF avviene principalmente all'interno del metodo [get_response];
- riga 60: il punto chiave è il parametro [allow_redirects=True]. Questo è il suo valore predefinito, ma volevamo sottolinearlo;
Quando si è in modalità [with_csrftoken=True]:
- i client iniziano la loro interazione con il server chiamando la route [/init-session_without_csftoken/type_response];
- il server risponde a questa richiesta con un reindirizzamento alla route [/init-session/type_response/csrf_token];
- Grazie al parametro [allow_redirects=True], questo reindirizzamento sarà seguito dalle [richieste] del client;
- il token CSRF si troverà nel risultato recuperato alle righe 72 e 74 associato alla chiave [csrf_token];
Quando si è in modalità [with_csrftoken=False]:
- (continua)
- i client iniziano la loro interazione con il server chiamando la route [/init-session/type_response];
- il server risponde a questa richiesta con un reindirizzamento alla route [/init-session/type_response];
- grazie al parametro [allow_redirects=True], questo reindirizzamento verrà seguito dal client [requests];
- non c'è alcun token CSRF da recuperare alle righe 81–82. La proprietà [self.__csrf_token] rimane quindi None (riga 36);
- righe 51–52: per tutte le richieste successive, il token CSRF, se esiste, viene aggiunto al percorso iniziale;
- righe 81–82: il nuovo token generato dal server per ogni nuova richiesta del client viene memorizzato localmente per essere restituito alla riga 52 con la richiesta successiva;
Inoltre, il metodo [init_session] cambia leggermente:
È importante ricordare che abbiamo creato un percorso [/init-session-without-csrftoken/<response-type>] per inizializzare il dialogo client/server senza un token CSRF. Tuttavia, abbiamo visto che il metodo [get_response] chiamato alla riga 12 del codice aggiunge sistematicamente il token CSRF memorizzato in [self.__csrf_token] alla fine dell'URL del servizio. Ecco perché, alla riga 6 del codice, rimuoviamo questo token CSRF se esiste.
Questo è tutto. Per il test, eseguiremo:
- the console clients [main, main2, main3];
- le classi di test [Test1HttpClientDaoWithSession] e [Test2HttpClientDaoWithSession];
impostando successivamente il parametro di configurazione [with_csrftoken] su True e poi su False.

Ecco un esempio dei log ottenuti eseguendo il client [main json] con [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
Se osserviamo i token CSRF ricevuti in successione, vediamo che sono tutti diversi.