Django 在旧数据库上运行迁移 1.将您的密码转换为 Django 支持的形式2.创建您自己的密码哈希器,将您现有的密码封装在 Django 中默认启用的密码中3.强制每个用户在访问新系统之前重置密码

问题描述

我正在使用旧数据库并创建了自定义用户模型。我正在努力设置注册和身份验证功能。我创建了用户管理器,在用户模型中,我为 django 添加了一些字段,如 is_staff、is_active、date_joined。当我运行迁移时,旧表仍然没有我在模型中添加的列。它真的应该改变旧数据库吗?

class TbUser(AbstractBaseUser,PermissionsMixin):
    id = models.CharField(primary_key=True,max_length=40)
    usname = models.CharField(max_length=40,blank=True,null=True,unique=True)
    psword = models.CharField(max_length=255,null=True)
  
    # added columns
    is_staff = models.BooleanField(default=False)
    is_active = models.BooleanField(default=True)
    date_joined = models.DateTimeField(default=timezone.Now)

    objects = TbUserManager()

    USERNAME_FIELD = 'usname'
    required_FIELDS = []

    class Meta:
        managed = False
        db_table = 'tb_user'

此外,当我创建超级用户时,出现以下错误

django.db.utils.OperationalError: (1054,"UnkNown column 'tb_user.password' in 'field list'")

虽然用户管理器看起来像这样

class TbUserManager(BaseUserManager):
    
    def create_user(self,email,psword=None,**kwargs):
        if not email:
            raise ValueError('Users must have a valid email address.')
        if not kwargs.get('usname'):
            raise ValueError('Users must have a valid username.')
        user = self.model(
            email=self.normalize_email(email),usname=kwargs.get('usname')
        )
        user.psword(psword)
        user.save()
        return user

    def create_superuser(self,psword,**kwargs):
        user = self.create_user(email,**kwargs)

        user.is_superuser = True
        user.save()

        return user

我真的不知道错误在哪里找到了 tb_user.password 因为我已将所有重命名psword

如果您需要一些详细信息,请随时询问。

编辑:

我发现密码错误是由于模型命名psword造成的,有没有办法告诉django这是密码字段?例如:USER_PASSWORD='psword'

解决方法

您得到的错误是由于 password 字段(以及 last_login 字段)已经在 AbstractBaseUser 中定义,并且如果您想使用 Django 验证,您必须使用该列才能使其正常工作。

最好将旧数据库转换为与 Django 身份验证系统兼容的形式。

您可能会遇到的第二个问题是 how Django stores passwords。由于您已经拥有现有数据,因此用户密码可能已经以某种形式存储在数据库中。您可以通过 3 种不同的方式修复它。

1.将您的密码转换为 Django 支持的形式。

如果您的旧系统以纯文本或使用任何方法 already supported by Django 存储所有密码,您只需将密码转换为其中之一即可。

我建议只使用一个默认启用的散列器,或者从下面描述的解决方案中选择另一种解决方案。

2.创建您自己的密码哈希器,将您现有的密码封装在 Django 中默认启用的密码中。

例如,如果您的旧方法使用 md5 对密码进行散列,您可以编写自己的密码散列器,它将采用现有的编码密码并将其放入现有密码散列器之一(如 django.contrib.auth.hashers.PBKDF2PasswordHasher) .

然后,您的设置可能如下所示:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher','django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher','your_auth_app.hashers.PBKDF2LegacyPasswordHasher',]

通过该配置,Django 将知道如何验证用户提供的密码,但它也会在用户登录时自动将其密码转换为列表中的第一个哈希器(因为 Django 将有权访问明文密码,它可以自己编码)

3.强制每个用户在访问新系统之前重置密码。

只需在旧密码前面添加一个 ! 字符,Django 就知道它不能用于登录。用户仍然可以使用密码重置电子邮件功能重置密码。


由于您提供了旧系统中现有密码哈希的示例,因此我将添加一个与您的情况更相关的示例。

密码开头的

$2a 表示 bcrypt。但是它在旧系统中的计算方式无法轻易确定,必须在旧系统的代码中查找,因为 bcrypt 可能用于另一种算法或任何其他密码操作方案之上.

但是由于 bcrypt 本身已经足够安全,您不需要将这种类型的散列包装在另一层散列中。如果您以前的系统不是简单地通过 bcrypt 或首先通过 sha256 然后通过 bcrypt,则可能需要实现您自己的密码散列器。

如果你通过阅读旧系统的代码很难确定,或者你根本做不到,你可以通过试错来检查。要检查 Django 内置的 2 个变体,首先通过安装 django[bcrypt] 来安装所需的库。

接下来,确保在 Django 设置中启用了您要测试的两个哈希器,最好将 PASSWORD_HASHERS 设置设置为:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.BCryptPasswordHasher',]

接下来,在您的旧系统中创建一个新用户帐户(或获取任何您知道密码的现有用户帐户),从中复制密码哈希并在其前面添加一个 bcrypt$ 前缀(最后一个在此修改后,此散列的形式应以 bcrypt$$2a$ 开头。注意双 $ 符号)。使用准备好的散列,使用 ./manage.py shell 打开管理控制台并执行:

from django.contrib.auth.hashers import check_password

check_password('your known password','your_modified_password_hash')

如果此函数返回 True,则您的旧系统使用普通 bcrypt。如果结果为 False,请将 bcrypt 替换为 bcrypt_sha256 以检查第二个密码哈希并再次执行 check_password。如果成功,您的旧系统会在将密码传递给 bcrypt 之前用 sha256 包装密码。

如果任一测试成功,您需要做的就是分别添加 bcrypt$bcrypt_sha256$ 前缀,具体取决于您的测试结果,到旧系统的所有密码哈希。如果它们都不起作用,您需要查看旧系统代码以确定确切的散列方法。

如果您的旧系统使用带有 sha256 的 bcrypt(并且您需要对旧数据做的就是添加 bcrypt_sha256$ 前缀),我建议将 PASSWORD_HASHERS 恢复为默认值。如果您想继续使用旧的散列方法,您可以将您的方法移到 PASSWORD_HASHERS 列表的顶部,这样 Django 将使用此方法创建任何新帐户,并且不会将旧密码散列更改为另一个用户使用密码登录时的方法。


关于 BCryptSHA256PasswordHasher 的旁注。 Django 在默认密码哈希器套件中使用它,因为普通 bcrypt 具有最大密码长度限制。首先通过 SHA256 确保 bcrypt 算法的输入永远不会超过该限制,无论用户提供的实际密码长度如何。