首页 > 解决方案 > 切换到django 3并返回django 2后无法登录Django项目

问题描述

我有一个 Django 2.2 项目,它在一堆不同的服务器上运行,但它们使用相同的数据库。

我创建了一个分支来迁移到 Django 3,但并非所有服务器都会同时迁移。

我使用 Argon2:

# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers  
PASSWORD_HASHERS = [  
 'django.contrib.auth.hashers.Argon2PasswordHasher',  
 'django.contrib.auth.hashers.PBKDF2PasswordHasher',  
 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',  
 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',  
 'django.contrib.auth.hashers.BCryptPasswordHasher',  
]

当我在开发分支中切换到 django 3.2 时,一切正常。但是,当我回到 Django 2.2 时,我开始收到如下错误:

只需删除 cookie 并重新加载即可解决这些问题。所以我猜测它们与 django 3.1 中从 sha1 到 sha256的新默认散列算法的变化有关。

无论如何,重新加载后,页面工作。但是当我尝试登录时,它无法识别凭据。

然后我从备份中恢复了数据库并可以登录 django 2.2。

我再次尝试使用以下设置在 django 3.2 上运行:

DEFAULT_HASHING_ALGORITHM = 'sha1'

现在,当切换回 2.2 时,我在页面加载时没有收到错误(我不需要删除 cookie),但凭据仍然不起作用。

对我来说,切换到 django 3.2 后,数据库中密码的哈希值发生了变化。

django 3是否有可能重写数据库中的密码?任何人都可以指出解决方案或尝试的东西吗?

谢谢你。

标签: djangohashpasswordsdjango-upgrade

解决方案


解决方案 TL;DR

好吧,似乎如果你使用 Argon2 哈希器,Django 确实会更新存储的密码,如果你想暂时避免它,你必须更新到 Django 3.1,直到准备好移动到 3.2 或子类化哈希器。

比较存储的密码

由于我可以在 Django 3 中访问生产数据库(django 2.2)和本地数据库,因此我比较了用户的密码:

Django 2.2(2019 年 4 月发布)

算法:argon2类型:argon2i版本:19内存成本:512时间成本:2并行度:2:UVn **********哈希:QVt ************** ***

Django 3.2

算法:argon2品种:argon2id版本:19内存成本:102,400时间成本:2并行度:8:pHQzc2 ****************哈希:flj ******** ********

是的,它们是不同的!

Django 3.2 发行说明讲述了默认 Argon2 哈希器的变化:

django.contrib.auth

  • PBKDF2 密码哈希的默认迭代次数从 216,000 增加到 260,000。

  • Argon2 密码哈希的默认变体更改为 Argon2id。memory_cost 和并行度分别增加到 102,400 和 8 以匹配 argon2-cffi 默认值。

  • 增加 memory_cost 会将所需内存从 512 KB 推到 100 MB。这仍然相当保守,但会在内存受限的环境中导致问题。如果是这种情况,现有的哈希可以被子类化以覆盖默认值。

  • Argon2、MD5、PBKDF2、SHA-1 密码哈希器的默认盐熵从 71 位增加到 128 位。

这是改变的门票:

#30472 应该支持 Argon2id 并成为 Argon2PasswordHasher 的默认品种

查看中的代码django.contrib.auth.hashers,我可以看到密码已在以下位置修改check_password

def check_password(password, encoded, setter=None, preferred='default'):
    ...
    hasher_changed = hasher.algorithm != preferred.algorithm
    must_update = hasher_changed or preferred.must_update(encoded)
    is_correct = hasher.verify(password, encoded)
    ...

class Argon2PasswordHasher(BasePasswordHasher):
    ...
    def must_update(self, encoded):
        decoded = self.decode(encoded)
        current_params = decoded['params']
        new_params = self.params()
        ...

    def params(self):
        argon2 = self._load_library()
        # salt_len is a noop, because we provide our own salt.
        return argon2.Parameters(
            type=argon2.low_level.Type.ID,
            version=argon2.low_level.ARGON2_VERSION,
            salt_len=argon2.DEFAULT_RANDOM_SALT_LENGTH,
            hash_len=argon2.DEFAULT_HASH_LENGTH,
            time_cost=self.time_cost,
            memory_cost=self.memory_cost,
            parallelism=self.parallelism,
        )

此行将type=argon2.low_level.Type.IDargon2 类型从 更改argon2iargon2id。其余的变化很明显。

我不确定这是否是真正的过程,但我想它是这样的:

  • 你输入密码
  • 使用旧算法检查密码
  • 如果匹配,则使用新算法重新散列并保存

(如果我错了,我会很高兴知道)

回顾一下:我的问题

我的问题是我有几个不同的 Django 2 项目使用相同的公共核心代码和相同的数据库。尽管它们是不同的项目,但有许多用户可以访问所有这些项目。我想从最不敏感的地方开始逐步更新它们,看看是否出现错误。

解决方案 1

升级到 Django 3.1 而不是 3.2。这将允许我逐步更新不同的项目,而不会中断用户访问。一旦所有项目在 3.1 版本中运行了一段时间并修复了出现的任何错误,我就可以更有信心地将它们同时更新到 django 3.2。这是我已经测试过并且有效的(它不会更改密码)。

解决方案 2

子类化django.contrib.auth.hashers.Argon2PasswordHasher哈希,因此它不会更新密码。在设置中指向它,PASSWORD_HASHERS当所有项目在 django 3.2 中顺利运行时将其删除。


推荐阅读