将密码字段迁移到 Django

Posted

技术标签:

【中文标题】将密码字段迁移到 Django【英文标题】:Migrating a password field to Django 【发布时间】:2012-01-01 16:51:22 【问题描述】:

我以前使用过 Django(1.2 版),总的来说我喜欢它……它特别擅长快速启动和运行一个全新的项目。但是,在这种情况下,我正在重写现有系统并将其移动到 Python/Django。所以,我已经有一个 mysql 数据库,其中有一个“用户”表......这个表使用 MySQL SHA1 函数(无盐等)存储用户的密码。

作为迁移的一部分,我将修复一些数据建模缺陷并移植到 PostgreSQL。

我真的很想使用 django.contrib.auth,但我不清楚我需要做什么。我已经阅读了文档,并且知道我可以将所需的用户信息和我拥有的“额外”信息分开并放入 UserProfile。

但是,如何处理存储在 MySQL 数据库中的密码?

以前有人处理过吗?你采取了什么方法?

【问题讨论】:

【参考方案1】:

您可以将其直接放入 user_password 字段 - 请参阅 the Django docs。由于您没有盐,请尝试使用sha1$$password_hash 格式。我还没有调查过它是否适用于空白盐,但这可能是您能够在不破解 django.contrib.auth 或编写自己的身份验证后端的情况下迁移它的唯一方法。

否则,您可以为用户设置一个不可用的密码(规范的做法是将字段设置为!)并将他们指向忘记密码系统。

【讨论】:

澄清一下,Dougai 建议将密码哈希放入 django,以 6 个文字字符“sha1$$”为前缀,如果我的密码是“密码”,则在 mysql 中,哈希密码字段将是5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 所以你必须将sha1$$5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 插入到数据库中。 我终于回到了这个项目并有机会测试它。据说我报告说这没有没有工作。 @DavidS,这大概是因为relevant code does assert salt。我认为(尽管这是未经测试的)正确的方法是制作一个不需要盐的自定义密码哈希。即:子类django.contrib.auth.hashers.SHA1PasswordHasher,设置algorithm = "sha1unsalt",复制encode()不带assert,覆盖salt()返回'',将该类添加到settings.PASSWORD_HASHERS,修改DB中的密码启动与sha1unsalt$$。我认为这应该可行.... @Dougal。谢谢回复。这看起来是个不错的方法。一个问题。 PASSWORD_HASHERS 在 1.3 中可用吗?看起来它只是当前“开发”分支中的文档,并且似乎正在为 1.4 开发。对吗? 我不知道从什么时候开始,但在 1.5 中,django.contrib.auth.hashers 中已经有 UnsaltedSHA1PasswordHasher【参考方案2】:

这是我为使事情正常工作所做的。我创建了一个自定义身份验证后端。注意:我使用电子邮件地址作为用户名。

这是我的代码:

from django.db.models import get_model
from django.contrib.auth.models import User
from hashlib import sha1

class MyUserAuthBackend(object):

    def check_legacy_password(self, db_password, supplied_password):
        return constant_time_compare(sha1(supplied_password).hexdigest(), db_password)


    def authenticate(self, username=None, password=None):
        """ Authenticate a user based on email address as the user name. """
        try:
            user = User.objects.get(email=username)

            if '$' not in user.password:
                if self.check_legacy_password(user.password, password):
                    user.set_password(password)
                    user.save()
                    return user
                else:
                    return None

            else:
                if user.check_password(password):
                    return user

        except User.DoesNotExist:
            return None


    def get_user(self, user_id):
        """ Get a User object from the user_id. """
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

然后我在 settings.py 中添加了以下内容:

AUTHENTICATION_BACKENDS = (
    'my_website.my_app.my_file.MyUserAuthBackend',
)

@Dougal 的建议似乎是针对 Django 的下一个版本,但我无法使用(我使用的是 1.3.1)。不过,这似乎是一个更好的解决方案。

【讨论】:

这是一个不错的解决方案,但需要注意几点。您可能希望使用恒定时间字符串比较:code.djangoproject.com/browser/django/tags/releases/1.3.1/…。您可能还想花时间使用 set_password 将旧密码迁移到 Django 格式,因为此时您已经有了密码。 @Mark - 我在开发解决方案时查看了 constant_time_compare 函数。我想我真的没有完全理解它的意义。此外,关于保存密码的要点。我会将它添加到上面的解决方案中。谢谢! constant_time_compare 函数可以防止定时攻击。 django-dev 线程的目的是一个很好的阅读:groups.google.com/group/django-developers/browse_thread/thread/… 谢谢,马克。我阅读了这些信息并决定用它更新答案/解决方案。【参考方案3】:

最新版本的 Django 为未加盐的旧密码提供了哈希器。只需将其添加到您的设置文件中:

PASSWORD_HASHERS = (
  ...
  'django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher',
)

【讨论】:

以上是关于将密码字段迁移到 Django的主要内容,如果未能解决你的问题,请参考以下文章

从 Django 1.11 迁移到 2.1:“fieldsets[2][1]”中有重复的字段

Django迁移无法将用户实例分配给用户字段

将用户帐户从Joomla迁移到Django

Django 数据库迁移:将两个字段合并为一个

将字段更改为多对多时的 Django 数据迁移

Django:迁移到 NullBooleanField 失败,IntegrityError“包含空值”