Django 在 URL 中加密主键

6 投票
2 回答
7744 浏览
提问于 2025-04-18 09:15

我看了很多资料,但还是不太明白怎么安全地加密我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 个回答

0
$(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
3

我写了一个库,可以帮助你实现这个功能: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 团队支持的所有版本。

撰写回答