Flask-Security 令牌是如何创建的?

How are Flask-Security tokens created?

我正在尝试了解 flask-security 令牌创建的工作原理。我的 flask 应用程序配置中有一个密钥,但我们的代码是开源的并且在 python 中,所以它不是很秘密,真的。

我可以在代码中访问创建令牌的序列化程序:

    serializer = current_app.extensions['security'].remember_token_serializer

然后我可以 serializer.loads(token) 并接收用户名和散列密码。

所以我的问题是:是什么阻止了恶意用户窃听我的流量(拥有密钥)来做同样的事情 - 获取 flask-security 令牌序列化器并反序列化我的令牌? flask 在创建令牌时是否使用了其他一些盐?但是,如果要重新启动服务,它将如何解密令牌?

我不完全确定实现细节以及令牌是如何生成的(但我很确定 flask security 使用 itsdangerous)。

令牌的主要用途不是存储您不想让任何人看到的秘密,因为令牌可以很容易地被任何人解码和查看(这就是采取额外步骤来存储密码的原因无法解密的哈希)。

令牌用于将数据从一个来源(客户端)发送到另一个来源(服务器)并确保数据不任何人修改或更改

是的,任何人都可以查看令牌中包含的内容,关键是没有密钥,任何人都无法篡改数据,如果攻击者生成自己的令牌,他必须使用secret key 否则令牌将被 flask 安全拒绝,因为它无效。

这里是我的意思的快速演示使用 itsdangerous(Flask 本身使用这个库进行会话)

假设在您的应用程序中,您仅通过检查用户是否将令牌中的参数 is_admin 设置为 'true' 来向用户授予管理员访问权限。

非管理员用户 John 进行身份验证并将令牌发送给他。

from itsdangerous import URLSafeSerializer

>>> s = URLSafeSerializer('secret key')
>>> s.dumps({'username': 'john', 'is_admin': 'false'})
'eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0.k45WPrVOG1Nrags0bwpVUbS7Vcw'

不知何故,攻击者截取了令牌,并且足够聪明,他知道令牌是 base64 编码的字符串,因此可以轻松地将它们解码回其原始形式。 Python 连标准库里都有这方面的模块,所以他也不用太紧张了。

import base64
>>> base64.b64decode('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0===')
b'{"username":"john","is_admin":"false"}'

就这样他能够解码我们的令牌并查看里面的内容(这就是密码被散列以提高安全性的原因)

因为他知道我们期望的参数,所以很自然地可以猜测任何将 is_admin 设置为 true 的用户都可以访问普通用户无法访问的内容,然后他继续生成他自己的令牌。

因为他没有我们的服务器秘密,他只是使用了一个随机的秘密

>>> t = URLSafeSerializer('fake key')
>>> t.dumps({'username': 'john', 'is_admin': 'true'})
'eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.fPUeGmfSaQWCysy5WWTmmMeuo6c'

好的,然后他将令牌发送到我们的服务器。

当我们尝试使用我们的序列化程序(已配置为使用我们的服务器密钥)解码令牌时,我们会收到错误消息。并且攻击者无法进入,因为他使用了错误的秘密来签署他的令牌。

>>> s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.fPUeGmfSaQWCysy5WWTmmMeuo6c')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 582, in loads
    return self.load_payload(self.make_signer(salt).unsign(s))
  File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 374, in unsign
    payload=value)
itsdangerous.BadSignature: Signature b'fPUeGmfSaQWCysy5WWTmmMeuo6c' does not match
>>>

服务器只能验证用它拥有的密钥签名的令牌。因此,只要您的服务器密钥没有被泄露,您就不必担心任何人将修改后的数据发送到您的应用程序。

你只需要确保你没有在令牌中存储 "real secrets"

# We can always load the correct token back
 s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6ImZhbHNlIn0.k45WPrVOG1Nrags0bwpVUbS7Vcw')
{'username': 'john', 'is_admin': 'false'}

即使您尝试将签名从正确的令牌附加到错误的数据,它也不会有效,您仍然会收到错误消息

s.loads('eyJ1c2VybmFtZSI6ImpvaG4iLCJpc19hZG1pbiI6InRydWUifQ.k45WPrVOG1Nrags0bwpVUbS7Vcw')

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 582, in loads
    return self.load_payload(self.make_signer(salt).unsign(s))
  File "/home/danidee/venv/lib/python3.5/site-packages/itsdangerous.py", line 374, in unsign
    payload=value)
itsdangerous.BadSignature: Signature b'k45WPrVOG1Nrags0bwpVUbS7Vcw' does not match

免责声明: 这可能不是 Flask 安全性中的确切实现,但我的回答应该让您了解幕后发生的事情。您可能还想阅读 JSON web tokens

并且不要将您的秘密存储在配置文件中,将其存储在 .env(这不会成为源代码管理的一部分)或将其设为环境变量和 EXPORT/SET生产环境中的变量。

结帐dotenv