web应用安全的黄金法则是,永远不要相信来自不可信来源的数据。 有时通过不可信的媒介来传递数据会非常方便。 密码签名后的值可以通过不受信任的途径传递,这样是安全的,因为任何篡改都会检测的到。
Django提供了用于签名的底层API,以及用于设置和读取被签名cookie的上层API,它们是web应用中最常使用的签名工具之一。
你可能会发现,签名对于以下事情非常有用:
222
¶当你使用 startproject
创建新的Django项目时,自动生成的 settings.py
文件会得到一个随机的SECRET_KEY
值。 这个值是保护签名数据的密钥 -- 它至关重要,你必须妥善保管,否则攻击者会使用它来生成自己的签名值。
Django的签名方法存放于django.core.signing
模块。
首先创建一个 Signer
的实例来对一个值签名:
>>> from django.core.signing import Signer
>>> signer = Signer()
>>> value = signer.sign('My string')
>>> value
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
这个签名会附加到字符串末尾,跟在冒号后面。
你可以使用unsign
方法来获取原始的值:
>>> original = signer.unsign(value)
>>> original
'My string'
如果签名或者值以任何方式改变,会抛出django.core.signing.BadSignature
异常:
>>> from django.core import signing
>>> value += 'm'
>>> try:
... original = signer.unsign(value)
... except signing.BadSignature:
... print("Tampering detected!")
通常,Signer
类使用SECRET_KEY
设置来生成签名。 你可以通过向Signer
构造器传递一个不同的密钥来使用它:
>>> signer = Signer('my-other-secret')
>>> value = signer.sign('My string')
>>> value
'My string:EkfQJafvGyiofrdGnuthdxImIJw'
Signer
(key=None, sep=':', salt=None)[source]¶返回一个signer,它使用key
来生成签名,并且使用sep
来分割值。 sep
不能是 URL安全的base64字母表中的字符。 字母表含有数字、字母、连字符和下划线。
salt
参数¶如果你不希望对每个特定的字符串都生成一个相同的签名哈希值,你可以在salt
类中使用可选的Signer
参数。 使用salt参数会同时用它和SECRET_KEY
初始化签名哈希函数:
>>> signer = Signer()
>>> signer.sign('My string')
'My string:GdMGD6HNQ_qdgxYP8yBZAdAIV1w'
>>> signer = Signer(salt='extra')
>>> signer.sign('My string')
'My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw'
>>> signer.unsign('My string:Ee7vGi-ING6n02gkcJ-QLHg6vFw')
'My string'
以这种方法使用salt会把不同的签名放在不同的命名空间中。 来自于单一命名空间(一个特定的salt值)的签名不能用于在不同的命名空间中验证相同的纯文本字符串。不同的命名空间使用不同的salt设置。 这是为了防止攻击者使用在一个地方的代码中生成的签名后的字符串,作为使用不同salt来生成(和验证)签名的另一处代码的输入。
不像你的SECRET_KEY
,你的salt参数可以不用保密。
TimestampSigner
是 Signer
的子类,它向值附加一个签名后的时间戳。 这可以让你确认一个签名后的值是否在特定时间段之内被创建:
>>> from datetime import timedelta
>>> from django.core.signing import TimestampSigner
>>> signer = TimestampSigner()
>>> value = signer.sign('hello')
>>> value
'hello:1NMg5H:oPVuCqlJWmChm1rA2lyTUtelC-c'
>>> signer.unsign(value)
'hello'
>>> signer.unsign(value, max_age=10)
...
SignatureExpired: Signature age 15.5289158821 > 10 seconds
>>> signer.unsign(value, max_age=20)
'hello'
>>> signer.unsign(value, max_age=timedelta(seconds=20))
'hello'
如果你希望保护一个列表、元组或字典,你可以使用签名模块的dumps
和 loads
函数来实现。 它们模仿了Python的pickle模块,但是在背后使用了JSON序列化。 JSON可以确保即使你的SECRET_KEY
被盗取,攻击者并不能利用pickle的格式来执行任意的命令:
>>> from django.core import signing
>>> value = signing.dumps({"foo": "bar"})
>>> value
'eyJmb28iOiJiYXIifQ:1NMg1b:zGcDE4-TCkaeGzLeW9UQwZesciI'
>>> signing.loads(value)
{'foo': 'bar'}
由于JSON的本质(列表和元组之间没有原生的区别),如果你传进来一个元组,你会从signing.loads(object)
得到一个列表:
>>> from django.core import signing
>>> value = signing.dumps(('a','b','c'))
>>> signing.loads(value)
['a', 'b', 'c']
dumps
(obj, key=None, salt='django.core.signing', compress=False)[source]¶返回URL安全,sha1签名的base64压缩的JSON字符串。 序列化的对象使用TimestampSigner
来签名。
2017年9月6日