При разработке на 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 (в секундах), и после проверки подписи будет проверена встроенная временная метка.
-
Вычисляется разница между текущим временем и временной меткой.
-
Если эта разница превышает
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. Важно! Внимание и советы по использованию
-
SECRET_KEY - это жизнь.
Вся эта механика подписей основана на SECRET_KEY из файла settings.py. Если этот ключ утечет, любой сможет создать действительную подпись, поэтому его нельзя раскрывать. (Не добавляйте в Git!)
-
Имейте в виду, что это не шифрование.
Передняя часть подписанных данных (Base64) может быть легко декодирована, чтобы увидеть оригинальное содержимое. Никогда не вставляйте чувствительные данные, такие как пароли или личные данные, непосредственно в
dumps(). (Например, user_id нормально, но user_password недопустимо.) -
Используйте salt для разделения подписей.
При использовании подписей для разных целей используйте параметр salt. Если salt разный, то даже при одинаковых данных результаты подписки будут совершенно разными.
# Используйте разные salt в зависимости от назначения
pw_reset_token = dumps(user.pk, salt='password-reset')
unsubscribe_link = dumps(user.pk, salt='unsubscribe')
Таким образом вы можете предотвратить атаки вроде повторного использования подписи 'ссылки для отписки' для 'сброса пароля'.
5. Основные примеры использования
-
URL для сброса пароля: Подписывайте идентификатор пользователя и временную метку и отправляйте по электронной почте (нет нужды хранить временный токен в БД)
-
Ссылка для подтверждения электронной почты: Ссылка для подтверждения электронной почты при регистрации
-
Безопасный
nextURL: Предотвращение изменения?next=/private/URL на злонамеренные сайты после входа в систему -
Временная ссылка для загрузки: Создание URL для доступа к файлу, который действителен только в течение ограниченного времени (
max_age) -
Многошаговая форма (Form Wizard): Защита от подделки данных при передаче данных формы на следующий шаг
Комментариев нет.