Lors du développement avec Django, il arrive que l'on doive envoyer des données au client via des paramètres d'URL, des champs cachés de formulaire, des cookies, etc., puis les récupérer. À ce moment-là, la question se pose : "Ces données n'ont-elles pas été modifiées par l'utilisateur en cours de route ?" Le module django.core.signing est un outil puissant pour résoudre ce problème.

Ce module offre une signature cryptographique plutôt qu'un chiffrement des données.

  • Chiffrement (X) : Cache le contenu des données.

  • Signature (O) : Le contenu des données peut être exposé, mais garantit que les données n'ont pas été falsifiées.


1. Usage principal : dumps() et loads()



Le cœur du module signing est dumps() et loads(). Cela permet de créer une chaîne signée à partir d'objets Python ou de restaurer des objets à partir d'une chaîne signée après validation.

dumps() : Convertir un objet en chaîne signée

dumps() prend des objets pouvant être sérialisés en JSON, comme des dictionnaires ou des listes, et renvoie une chaîne signée sûre pour l'URL.

from django.core.signing import dumps

# Données à signer
user_data = {'user_id': 123, 'role': 'user'}

# Signature des données et création de la chaîne.
signed_data = dumps(user_data)

print(signed_data)
# Exemple de sortie : eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJ1c2VyIn0:1q51oH:F... (données:signature)

Le résultat est sous la forme [données encodées]:[signature]. La première partie est le contenu des données encodé en Base64, de sorte que tout le monde peut le décoder pour voir l'original. La partie arrière est la signature (valeur de hachage) générée par le SECRET_KEY de Django.

loads() : Restaurer une chaîne signée en objet (validation)

loads() prend une chaîne créée par dumps() et valide la signature. Si la signature est valide, elle renvoie l'objet d'origine, sinon, elle lève l'exception BadSignature.

from django.core.signing import loads, BadSignature

try:
    # Restaurer les données signées en objet d'origine (validation)
    unsigned_data = loads(signed_data)
    print(unsigned_data)
    # Sortie : {'user_id': 123, 'role': 'user'}

except BadSignature:
    print("Les données ont été falsifiées ou la signature est invalide.")

# Que se passe-t-il si les données ont changé d'un caractère ?
tampered_data = signed_data.replace('user', 'admin')

try:
    loads(tampered_data)
except BadSignature:
    print("Les données falsifiées lèvent une exception BadSignature.")

Vous devez toujours entourer le code avec try...except BadSignature.


2. Caractéristiques principales : Où les données sont-elles stockées ?

Une des plus grandes caractéristiques de django.core.signing est son côté "Sans état (stateless)".

Les chaînes créées avec dumps() ne sont stockées nulle part sur le serveur (dans la base de données ou le cache). Toutes les informations (données et signature) sont contenues dans la chaîne signée elle-même.

Le serveur transmet cette chaîne au client (par URL, cookies, champs de formulaire, etc.), puis lorsque le client soumet à nouveau cette valeur, il valide la signature à la volée à l'aide de SECRET_KEY. Cela permet d'économiser complètement l'espace de stockage sur le serveur, rendant le système léger et efficace.


3. Configuration de la durée de validité : Le secret de max_age



"Comment peut-on définir une durée de validité si les données ne sont pas stockées sur le serveur ?"

L'option max_age inclut un timestamp (information temporelle) lors de la signature avec dumps().

Lorsque vous appelez loads(), en passant l'argument max_age (en secondes), il vérifie le timestamp intégré après avoir validé la signature.

  1. Calcule la différence entre le temps actuel et le timestamp.

  2. Si cette différence dépasse max_age, lève l'exception SignatureExpired, même si la signature n'a pas été falsifiée.

from django.core.signing import dumps, loads, SignatureExpired, BadSignature
import time

# 1. Création de la signature (le temps au moment de la signature est enregistré)
signed_data = dumps({'user_id': 456})

# 2. Validation avec une durée de validité de 10 secondes (immédiatement) -> succès
try:
    data = loads(signed_data, max_age=10)
    print(f"Validation réussie : {data}")
except SignatureExpired:
    print("La signature a expiré.")
except BadSignature:
    print("La signature est invalide.")


# 3. Attendre 5 secondes
time.sleep(5)

# 4. Validation avec une durée de validité de 3 secondes (déjà passé 5 secondes) -> échec
try:
    data = loads(signed_data, max_age=3)
    print(f"Validation réussie : {data}")
except SignatureExpired:
    print("La signature a expiré. (max_age=3)")
except BadSignature:
    print("La signature est invalide.")

Il s'agit encore une fois d'une manière qui ne stocke pas la durée d'expiration sur le serveur, mais utilise le timestamp intégré pour un fonctionnement stateless.


4. Important ! Avertissements et conseils pratiques

  1. Le SECRET_KEY est essentiel.

    Tous ces mécanismes de signature fonctionnent sur la base du SECRET_KEY dans settings.py. Si cette clé est divulguée, quiconque peut générer une signature valide, donc elle ne doit jamais être exposée publiquement. (Ne la mettez pas sur Git !)

  2. Rappelez-vous que ce n'est pas du chiffrement.

    La première partie des données signées (Base64) peut être facilement décodée par quiconque pour voir le contenu d'origine. Ne mettez jamais de données sensibles, comme des mots de passe ou des informations personnelles, directement dans dumps(). (Ex : user_id est acceptable, mais user_password ne l'est pas.)

  3. Utilisez des sels pour séparer les signatures.

    Lorsque vous utilisez des signatures pour différents objectifs, utilisez le paramètre salt. Si le sel est différent, même les données identiques produiront des résultats de signature complètement différents.

# Utilisation de sels différents selon l'objectif
pw_reset_token = dumps(user.pk, salt='password-reset')
unsubscribe_link = dumps(user.pk, salt='unsubscribe')
Cela empêche des attaques où la signature pour 'lien de désinscription' pourrait être réutilisée pour 'réinitialisation de mot de passe'.

5. Cas d'utilisation principaux

  • URL de réinitialisation de mot de passe : Signature de l'ID utilisateur et du timestamp pour envoi par email (pas besoin de stocker de token temporaire dans la DB)

  • Liens de vérification par email : Lien de vérification par email lors de l'inscription

  • URL next sécurisée : Empêche les URL de redirection ?next=/private/ d'être modifiées vers des sites malveillants

  • Liens de téléchargement temporaires : Création d'URL d'accès à des fichiers valables pendant une durée spécifique (max_age)

  • Formulaires en plusieurs étapes (Form Wizard) : Empêcher la falsification des données lorsqu'on passe les données du formulaire précédent à l'étape suivante