Bij de ontwikkeling van Django kan het voorkomen dat je gegevens naar de client moet sturen via URL-parameters, verborgen velden van formulieren of cookies, en deze later weer moet ontvangen. Op dat moment komt de vraag op: "Zijn deze gegevens onderweg door de gebruiker gewijzigd?" Het django.core.signing module is een krachtig hulpmiddel om dit probleem op te lossen.

Deze module biedt geen encryptie, maar cryptografische ondertekening aan.

  • Encryptie (X): Verbergt de inhoud van de gegevens.

  • Handtekening (O): De inhoud van de gegevens kan worden onthuld, maar garandeert dat de gegevens niet zijn gewijzigd.


1. Kerngebruik: dumps() en loads()



De kern van de signing module zijn dumps() en loads(). Het maakt een ondertekende string van een Python-object, of herstelt en valideert een ondertekende string naar een object.

dumps(): Converteer een object naar een ondertekende string

dumps() accepteert JSON-serialiseerbare objecten zoals dictionaries en lijsten, en retourneert een URL-veilige ondertekende string.

from django.core.signing import dumps

# Gegevens om te ondertekenen
user_data = {'user_id': 123, 'role': 'user'}

# Onderteken de gegevens en maak een string aan.
signed_data = dumps(user_data)

print(signed_data)
# Voorbeeld van output: eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJ1c2VyIn0:1q51oH:F... (gegevens:ondertekening)

Het resultaat is van de vorm [gecodeerde gegevens]:[ondertekening]. Het eerste deel is de Base64-gecodeerde originele gegevens, wat betekent dat iedereen deze kan decoderen om de originele gegevens te zien. Het achterste deel is de ondertekening (hashwaarde) die is gegenereerd met Django's SECRET_KEY.

loads(): Herstel een ondertekende string naar een object (validatie)

loads() accepteert een string die is gemaakt met dumps() en valideert de ondertekening. Als de ondertekening geldig is, retourneert het het originele object; is het ongeldig, dan wordt er een BadSignature uitzondering opgegooid.

from django.core.signing import loads, BadSignature

try:
    # Herstel de ondertekende gegevens naar het originele object (validatie)
    unsigned_data = loads(signed_data)
    print(unsigned_data)
    # Output: {'user_id': 123, 'role': 'user'}

except BadSignature:
    print("De gegevens zijn gemanipuleerd of de ondertekening is ongeldig.")

# Wat als de gegevens ook maar 1 teken zijn gewijzigd?
tampered_data = signed_data.replace('user', 'admin')

try:
    loads(tampered_data)
except BadSignature:
    print("Gemanipuleerde gegevens veroorzaken een BadSignature uitzondering.")

Gebruik altijd de try...except BadSignature syntaxis.


2. Kernkenmerk: Waar worden de gegevens opgeslagen?

Het grootste kenmerk van django.core.signing is "Stateless" (staatloos).

De string die is gemaakt met dumps() wordt nergens in de database of de cache van de server opgeslagen. Alle informatie (gegevens en ondertekening) is opgenomen in de ondertekende string zelf.

De server verstrekt deze string aan de client (via URL, cookies, formulierinvoer, enz.), en wanneer de client deze waardes weer indient, valideert de server deze eenvoudigweg met behulp van de SECRET_KEY. Dit maakt het uiterst licht en efficiënt, omdat er geen opslagcapaciteit op de server wordt gebruikt.


3. Geldigheidstijd instellen: Het geheim van max_age



"Als de gegevens niet op de server zijn opgeslagen, hoe kunnen we dan de geldigheidstijd instellen?"

De max_age optie voegt een tijdstempel (tijdinformatie) bij de handtekening van de gegevens toe wanneer dumps() wordt aangeroepen.

Als de loads() functie wordt aangeroepen met de max_age parameter (in seconden), controleert deze de ingebouwde tijdstempel na de validatie van de handtekening.

  1. Bereken het verschil tussen de huidige tijd en de tijdstempel.

  2. Als dit verschil groter is dan de max_age, zal de SignatureExpired uitzondering worden opgegooid, zelfs als de handtekening niet is vervalst.

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

# 1. Maak een handtekening aan (de tijd op dat moment wordt vastgelegd)
signed_data = dumps({'user_id': 456})

# 2. Valideer met een geldigheidstermijn van 10 seconden (direct) -> Succes
try:
    data = loads(signed_data, max_age=10)
    print(f"Validatie succesvol: {data}")
except SignatureExpired:
    print("De handtekening is verlopen.")
except BadSignature:
    print("De handtekening is ongeldig.")


# 3. Wacht 5 seconden
time.sleep(5)

# 4. Valideer met een geldigheidstermijn van 3 seconden (al 5 seconden voorbij) -> Mislukking
try:
    data = loads(signed_data, max_age=3)
    print(f"Validatie succesvol: {data}")
except SignatureExpired:
    print("De handtekening is verlopen. (max_age=3)")
except BadSignature:
    print("De handtekening is ongeldig.")

Dit is ook geen manier om de vervaltijden op de server op te slaan, maar, maakt gebruik van de tijdstempel die is opgenomen in de handtekening, wat een stateless methode is.


4. Belangrijk! Waarschuwingen en tips voor gebruik

  1. SECRET_KEY is levensbelangrijk.

    Dit hele handtekeningmechanisme werkt op basis van de SECRET_KEY in settings.py. Als deze sleutel wordt gelekt, kan iedereen geldige handtekeningen genereren, dus deze mag absoluut niet openbaar worden gemaakt. (Verstuur deze niet naar Git!)

  2. Vergeet niet dat het geen encryptie is.

    Het voorste deel van de ondertekende gegevens (Base64) kan door iedereen gemakkelijk worden gedecodeerd om de originele inhoud te zien. Plaats nooit gevoelige gegevens zoals wachtwoorden of persoonlijke informatie direct in dumps(). (Bijv. user_id is okay, maar user_password niet.)

  3. Scheider de handtekeningen met salt.

    Gebruik de salt parameter wanneer je handtekeningen voor verschillende doeleinden wilt gebruiken. Als de salt anders is, zal de ondertekening voor dezelfde gegevens volledig anders zijn.

# Gebruik verschillende salt afhankelijk van het doel
pw_reset_token = dumps(user.pk, salt='password-reset')
unsubscribe_link = dumps(user.pk, salt='unsubscribe')
Dit voorkomt aanvallen zoals het hergebruiken van de handtekening voor de 'afmeldlink' voor het 'wachtwoord reset'.

5. Belangrijkste gebruiksvoorbeelden

  • Wachtwoordreset URL: Onderteken de ID van de gebruiker en het tijdstempel en verzend deze per e-mail (geen tijdelijke token in DB nodig)

  • E-mail verificatielink: E-mail verificatielink bij nieuwe aanmelding

  • Veilige next URL: Voorkom dat de ?next=/private/ URL, waar na inloggen naar omgeleid kan worden, naar een kwaadaardige site wordt gewijzigd

  • Tijdelijke downloadlink: Genereer een URL voor bestandstoegang die slechts een beperkte tijd (max_age) geldig is

  • Meerdere stadia van een formulier (Form Wizard): Voorkom dat gegevens van een eerder stadium worden gemanipuleerd bij het doorgeven naar het volgende stadium