При разработке на Django иногда требуется отправлять данные клиенту через параметры URL, скрытые поля форм или куки, а затем получать их обратно. В это время возникает вопрос: "Не изменил ли пользователь эти данные по пути?" Модуль django.core.signing является мощным инструментом для решения этой проблемы.

Этот модуль предоставляет криптографическую подпись (Cryptographic Signing) вместо шифрования (Encryption) данных.

  • Шифрование (X): скрывает содержимое данных.

  • Подпись (O): содержимое данных может быть открыто, но гарантирует, что данные не были подделаны.


1. Основные методы использования: dumps() и loads()



Основой модуля signing являются dumps() и loads(). Они превращают объекты Python в подписанные строки и восстанавливают объекты из подписанных строк, проверяя подпись.

dumps(): Преобразование объекта в подписанную строку

dumps() принимает такие объекты, как словари и списки, которые могут быть сериализованы в JSON, и возвращает безопасную для URL подписанную строку.

from django.core.signing import dumps

# Данные для подписания
user_data = {'user_id': 123, 'role': 'user'}

# Подписываем данные и создаем строку.
signed_data = dumps(user_data)

print(signed_data)
# Пример вывода: eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJ1c2VyIn0:1q51oH:F... (данные:подпись)

Результат имеет вид [кодированные данные]:[подпись]. Первая часть представляет собой кодированные данные в формате Base64, поэтому каждый может декодировать и увидеть оригинал. Задняя часть представляет собой подпись (хеш), созданную с помощью SECRET_KEY Django.

loads(): Восстановление объекта из подписанной строки (проверка)

loads() принимает строку, созданную dumps(), и проверяет подпись. Если подпись действительна, возвращается оригинальный объект, если нет, выбрасывается исключение BadSignature.

from django.core.signing import loads, BadSignature

try:
    # Восстанавливаем оригинальный объект из подписанных данных (проверка)
    unsigned_data = loads(signed_data)
    print(unsigned_data)
    # Вывод: {'user_id': 123, 'role': 'user'}

except BadSignature:
    print("Данные были подделаны или подпись недействительна.")

# Если данные изменены хотя бы на один символ?
tampered_data = signed_data.replace('user', 'admin')

try:
    loads(tampered_data)
except BadSignature:
    print("Подделанные данные вызовут исключение BadSignature.")

Всегда используйте try...except BadSignature при работе с этим методом.


2. Основные характеристики: Где хранятся данные?

Основная характеристика django.core.signing заключается в том, что это "безсостояние (stateless)".

Строки, созданные с помощью dumps(), нигде не сохраняются на сервере, ни в базе данных, ни в кэше. Вся информация (данные и подпись) содержится в самой подписанной строке.

Сервер передает эту строку клиенту (через URL, куки, поля форм и т.д.), а затем, когда клиент отправляет это значение обратно, он просто проверяет валидность с помощью SECRET_KEY. Благодаря этому сервер не использует место для хранения и работает очень эффективно.


3. Настройка времени действия: секрет max_age



"Если данные не хранятся на сервере, как можно установить время действия?"

Опция max_age включает временную метку (timestamp) в данные при подписании с помощью dumps().

Когда вы вызываете loads(), передайте аргумент max_age (в секундах), и после проверки подписи будет проверена встроенная временная метка.

  1. Вычисляется разница между текущим временем и временной меткой.

  2. Если эта разница превышает max_age, то даже если подпись не была подделана, выбрасывается исключение SignatureExpired.

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

# 1. Генерация подписи (в это время записывается временная метка)
signed_data = dumps({'user_id': 456})

# 2. Проверка в течение 10 секунд (сразу) -> успех
try:
    data = loads(signed_data, max_age=10)
    print(f"Проверка успешна: {data}")
except SignatureExpired:
    print("Подпись истекла.")
except BadSignature:
    print("Подпись недействительна.")


# 3. Ожидание 5 секунд
time.sleep(5)

# 4. Проверка с временем действия 3 секунды (прошло уже 5 секунд) -> неудача
try:
    data = loads(signed_data, max_age=3)
    print(f"Проверка успешна: {data}")
except SignatureExpired:
    print("Подпись истекла. (max_age=3)")
except BadSignature:
    print("Подпись недействительна.")

Это также является вариантом без хранения времени действия на сервере, использует временную метку, которая содержится в самой подписи, что делает его stateless.


4. Важно! Внимание и советы по использованию

  1. SECRET_KEY - это жизнь.

    Вся эта механика подписей основана на SECRET_KEY из файла settings.py. Если этот ключ утечет, любой сможет создать действительную подпись, поэтому его нельзя раскрывать. (Не добавляйте в Git!)

  2. Имейте в виду, что это не шифрование.

    Передняя часть подписанных данных (Base64) может быть легко декодирована, чтобы увидеть оригинальное содержимое. Никогда не вставляйте чувствительные данные, такие как пароли или личные данные, непосредственно в dumps(). (Например, user_id нормально, но user_password недопустимо.)

  3. Используйте salt для разделения подписей.

    При использовании подписей для разных целей используйте параметр salt. Если salt разный, то даже при одинаковых данных результаты подписки будут совершенно разными.

# Используйте разные salt в зависимости от назначения
pw_reset_token = dumps(user.pk, salt='password-reset')
unsubscribe_link = dumps(user.pk, salt='unsubscribe')
Таким образом вы можете предотвратить атаки вроде повторного использования подписи 'ссылки для отписки' для 'сброса пароля'.

5. Основные примеры использования

  • URL для сброса пароля: Подписывайте идентификатор пользователя и временную метку и отправляйте по электронной почте (нет нужды хранить временный токен в БД)

  • Ссылка для подтверждения электронной почты: Ссылка для подтверждения электронной почты при регистрации

  • Безопасный next URL: Предотвращение изменения ?next=/private/ URL на злонамеренные сайты после входа в систему

  • Временная ссылка для загрузки: Создание URL для доступа к файлу, который действителен только в течение ограниченного времени (max_age)

  • Многошаговая форма (Form Wizard): Защита от подделки данных при передаче данных формы на следующий шаг