Django 在 URL 中加密主键
我看了很多资料,但还是不太明白怎么安全地加密我Django应用中的主要ID,以防止在网址中泄露。
我的网址是这样的:
http://www.example.com/primary1_id/primary2_id/testing/
比如说:
http://www.example.com/3/7/testing/
我想把上面的网址展示给用户,格式如下:
http://www.example.com/623477897ghfjs23879/7829yfgweh/testing/ #encrypted key instead of primary id
在我的视图中,我需要能够从加密的密钥中解码出primary1_id和primary2_id。
请问我该如何以最好的方式处理这个问题?
提前谢谢大家!
2 个回答
$(function() { //on page load
$( ".exampleclass" ).each(function( index ) { //replace each url
var url = $(this).attr('href'); //get current url
var encodedUrl = encodeURIComponent(url); //enconde url
$(this).attr("href", encodedUrl); /replace
});
});
结果:
http://10.91.161.54:8181/validate_/listActivi/314/
到
http://10.91.161.54:8181/validate_/%2Fvalidate_%2FlistActivi%2F314%2F%20
我写了一个库,可以帮助你实现这个功能:django-encrypted-id。下面是一个示例模型:
from django.db import models
from encrypted_id.models import EncryptedIDModel
class Foo(EncryptedIDModel):
text = models.TextField()
通过继承自 EncryptedIDModel,你的模型实例会有一个 .ekey 属性。它们看起来是这样的:
In [1]: from tapp.models import Foo
In [2]: f = Foo.objects.create(text="asd")
In [3]: f.id
Out[3]: 1
In [4]: f.ekey
Out[4]: 'bxuZXwM4NdgGauVWR-ueUA..'
你可以进行反向查找:
In [5]: from encrypted_id import decode
In [6]: decode(f.ekey)
Out[6]: 1
如果你不能从这个辅助基类继承,也没关系,你可以直接使用 encrypted_id 包里的 ekey() 函数:
In [7]: from encrypted_id import ekey
In [8]: from django.contrib.auth.models import User
In [9]: ekey(User.objects.get(pk=1))
Out[9]: 'bxuZXwM4NdgGauVWR-ueUA..'
要进行反向查找,你有两个可用的助手。第一个是 EncryptedIDManager 提供的,如果你从 EncryptedIDModel 继承并且没有重写 .objects,它会默认使用这个:
In [10]: Foo.objects.get_by_ekey(f.ekey)
Out[10]: <Foo: Foo object>
但有时候你可能更喜欢这种形式:
In [11]: Foo.objects.get_by_ekey_or_404(f.ekey)
Out[11]: <Foo: Foo object>
这个方法的效果是一样的,但它会抛出 Http404,而不是 DoesNotExist,这样可以在视图中使用。
如果你的管理器没有继承自 EncryptedIDManager,你可以使用:
In [12]: e = ekey(User.objects.first())
In [13]: e
Out[13]: 'bxuZXwM4NdgGauVWR-ueUA..'
In [14]: get_object_or_404(User, e)
Out[14]: <User: amitu>
encrypted_id.get_object_or_404,以及 EncryptedIDManager.get_by_ekey 和 EncryptedIDManager.get_by_ekey_or_404 也可以接受额外的关键字参数,用于过滤。
如果你感兴趣,生成的 ID 匹配使用的正则表达式是:
"[0-9a-zA-Z-_]+.{0,2}"
如果你使用 smarturls,你可以使用这样的 URL 模式:
"/<ekey:foo>/"
我推荐使用这个加密 ID 而不是 UUID,因为 UUID 有一些显著的问题需要考虑(简单来说:它们在磁盘和内存中占用更多空间,索引效率也不如整数 ID),如果你的目标只是让 URL 不容易被猜到,加密 ID 是更好的选择。
如果你对使用的加密方式感兴趣:我使用的是 AES,来自 pycrypto 库,并且使用 SECRET_KEY 作为密码(SECRET_KEY[:24])和 IV(SECRET_KEY[-16:]),在 AES.CBC 模式下。一般来说,建议不要使用静态 IV,但 CBC 模式可以缓解一些静态 IV 带来的问题。你可能会问,静态 IV 有什么问题:如果明文 "abc" 和 "abe" 被加密,前两个字节会是相同的。现在这对我们来说并不是一个严重的问题,因为我加密的明文在负载的开头使用了 CRC32,所以即使你有 ID 1 和 11,攻击者也无法判断它们的第一个字符是否相同。
这个库还支持你因为某些原因需要更换 SECRET_KEY 的场景,因此用旧 SECRET_KEY 加密的 URL 在你更改后仍然可以解码(只要你在 SECRET_KEYS 设置中存储旧版本)。为了进行解密,库会尝试每个密钥,并比较数据的 CRC32,以确保(尽可能确保)我们正确解密了。
如果你遇到任何问题,请随时在 encrypted-id 的 GitHub 仓库 提出问题,我很乐意提供帮助。这个库支持 Python 2.7 和 3.5,以及 Django 团队支持的所有版本。