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

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

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

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

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

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

【问题讨论】:

    标签: python mysql django postgresql passwords


    【解决方案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',
    )
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-21
      • 2012-04-10
      • 2014-11-21
      • 2017-02-03
      • 2021-02-18
      • 2013-02-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多