如何在Python中生成唯一的身份验证令牌?

18

我正在尝试为我的安卓应用程序在Flask中编写基于令牌的身份验证。为此,我需要使用唯一令牌来验证用户。

Itsdangerous库提供了一个JSONWebSignatureSerializer函数,可以使用它创建JWT令牌。所以我的第一个问题是,对于基于移动设备的身份验证,使用JWT是否安全?

其次,我对Django Rest Framework如何生成其令牌进行了一些研究。

def generate_key(self):
    return binascii.hexlify(os.urandom(20)).decode()  

这个令牌是唯一的还是随机的?在移动应用程序身份验证中应该使用哪个?

在 Python 中生成移动应用程序的唯一令牌的最佳方法是什么?


1
为什么不使用像flask-jwt这样的解决方案呢?重新发明轮子通常不是一个好主意,特别是对于安全来说。 - Burhan Khalid
6个回答

27

您可以像使用内置的uuid模块一样使用它。3.6中发布的新secrets模块也能够创建唯一的令牌。

from uuid import uuid4

rand_token = uuid4()

下面的函数每次调用都会创建一个唯一的令牌。 os.urandom 方法返回 20 个随机字节作为字符串,binascii.hexlify 方法将这 20 个字节中的每个字节都转换为该字节的 2 位十六进制表示形式。这就是返回值两倍长的原因。

如果您想使用此方法并且需要特定长度的令牌,请使用所需长度的一半作为参数传递给os.urandom 方法。

def generate_key(self):
    return binascii.hexlify(os.urandom(20)).decode()

10

好的,这篇文章有些过时,但我还是要发表一下意见。您需要决定:您想要独特还是随机?请选择一个。

如果您需要独特的值,请使用UUID。UUID的整个目的就是确保您生成的值是唯一的。UUID代表通用唯一标识符。

如果您想要随机的值,请使用os.urandom。真正的随机结果无法受到唯一性限制!那样会使它们不再是随机的。事实上,这会使它们成为UUID。

现在,对于您的问题,您正在寻求身份验证令牌。这意味着您将其用于安全目的。UUID不是解决方案,生成安全数字是正确的解决方法。如果使用随机数而不是UUID,是否可能出现冲突?是的。但是除非您有数十亿用户,否则不太可能。您需要在此进行计算,但我的建议是:当您需要使用随机数时,请勿使用UUID。

哎呀。


3
UUID4有一个随机生成的部分。另外需要考虑的是:生成重复UUID的概率不为零!但它足够小,可以被认为是唯一的。 - nnov

2

1
我已经调查过了,但是包括Django, Pyramid在内的Python框架都没有使用UUID来生成令牌。它们使用binascii.hexlify(os.urandom(20)).decode()。 - saprative
这个讨论有用吗?Django内置了uuid。https://dev59.com/42865IYBdhLWcg3wM7q0 - vielmetti

0
一个可能的解决方案是对令牌过期时间和用户名进行AES加密,这使得很容易发现过期的令牌,并且不需要额外的数据库空间来存储令牌。

如果有人找到了你的秘钥呢?他们将完全可以访问你网站上的所有用户账户... 这是一个糟糕的想法。 - Hack5
这意味着你的服务器安全性很差或者你使用了一个糟糕的密钥,此外你可以将其保存在内存中并随机生成它,用户在服务器重启时会被注销,但这听起来没问题。 - Patrik Radics

0
我为在Django模型中生成唯一令牌编写了一个小的辅助函数。您可以从模型的save()方法中调用它。它使用定义的函数生成候选令牌,搜索数据库中现有的行以查找该候选令牌。如果找到一个,则再次尝试,否则返回候选字符串。请注意,这里存在一个小的竞争条件,但是使用具有足够大输出范围的令牌函数时不太可能发生。
def generate_unique_token(Model,
                      token_field="token",
                      token_function=lambda: uuid.uuid4().hex[:8]):
"""
Generates random tokens until a unique one is found
:param Model: a Model class that should be searched
:param token_field: a string with the name of the token field to search in the model_class
:param token_function: a callable that returns a candidate value
:return: the unique candidate token
"""
unique_token_found = False
while not unique_token_found:
    token = token_function()
    # This weird looking construction is a way to pass a value to a field with a dynamic name
    if Model.objects.filter(**{token_field:token}).count() is 0:
        unique_token_found = True
return token

然后,您可以通过调用以下方法来查找唯一的令牌

token = generate_unique_token(MyModelInstance, "token_field_name")

它甚至支持使用其他生成令牌的方法。例如,如果您想使用完整的 uuid,只需像这样调用:

token = generate_unique_token(MyModel, "token_field_name", lambda: uuid.uuid4().hex)

这似乎是不必要的吧?根据算法,Guids/uuids几乎可以保证是唯一的。 - Moulde
也许吧,但如果你想获得一个更短的令牌,这真的非常有用。 - George Griffin
是的,UUID的目的就在于其名称中!Universally Unique IDs(通用唯一标识符)!这真是一个非常奇怪的答案。 - mlissner
UUID具有相当长的不幸特性。如果您只是截断令牌,那么您就会牺牲冲突抵抗力。您不能简单地将令牌保存到数据库中并假定该令牌将是唯一的。 - George Griffin

0

在生成安全随机令牌(OWASP建议)方面有一些建议(特别是这一部分)。遵循这些建议,我发现了专门用于此目的的secrets标准模块。

from secrets import token_bytes, token_hex, token_urlsafe

token_bytes: bytes = token_bytes()
token_hex: str = token_hex()
token_url: str = token_urlsafe()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接