在 Django 开发中,有时需要通过 URL 参数、表单的隐藏字段、Cookie 等向客户端发送数据并再接收回来。这时会产生 "这些数据是否被用户在中间修改了?" 的疑虑。 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 编码的,任何人都可以解码查看原始内容。 后面部分是由 Django 的 SECRET_KEY 生成的签名(哈希值)。
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、Cookie、表单字段等),当客户端再次提交此值时,只使用 SECRET_KEY 进行即时有效性验证。因此,它几乎不使用服务器的存储空间,效率非常高。
3. 设置有效时间: max_age 的秘密
“数据不会存储在服务器中,怎么能设置有效时间呢?”
max_age 选项是在 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("签名无效。")
这同样是 不是将过期时间存储在服务器中的方式,而是利用签名自身包含的时间戳的无状态方式。
4. 重要!注意事项及使用提示
-
SECRET_KEY 是生命线。
所有签名机制都是基于 settings.py 中的 SECRET_KEY 操作的。一旦该密钥被泄露,任何人都可以生成有效的签名,因此绝不能外泄。 (不要上传到 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: 将用户的 ID 和时间戳进行签名,发送到电子邮件中(无需在数据库中存储临时令牌)
-
电子邮件认证链接: 新用户注册时的电子邮件认证链接
-
安全的
nextURL: 防止登录后重定向?next=/private/URL 被更改为恶意网站 -
临时下载链接: 创建在特定时间(
max_age)内有效的文件访问 URL -
多步骤表单 (Form Wizard): 在将前一步表单数据传递到下一步时防止数据篡改
目前沒有評論。