将密码字段迁移到Django

7 投票
3 回答
3647 浏览
提问于 2025-04-17 06:37

我之前用过Django(1.2版本),总体来说我挺喜欢它的……特别是在快速启动一个全新项目方面表现得很好。不过这次我是在重写一个已有的系统,并把它迁移到Python/Django上。所以,我已经有一个MySQL数据库,里面有一个“users”表……这个表用MySQL的SHA1函数存储用户的密码(没有盐值等)。

在迁移的过程中,我打算修复一些数据建模上的缺陷,并且转到PostgreSQL数据库。

我真的很想使用django.contrib.auth,但我不太清楚该怎么做。我看过文档,知道可以把需要的用户信息和我额外的信息分开,然后放到UserProfile里。

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

有没有人处理过这个问题?你们采取了什么方法?

3 个回答

1

最近版本的Django提供了一种处理没有加盐的旧密码的方法。你只需要把这个添加到你的设置文件里:

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

你可以直接把密码放到 user_password 这个字段里 - 具体可以参考 Django 的文档。因为你没有盐值(salt),可以试试用 sha1$$password_hash 这种格式。我还没确认过用空盐值是否能行,但这可能是你在不修改 django.contrib.auth 或自己写认证后端的情况下,唯一能迁移的方法。

另外,你也可以给用户设置一个无效的密码(通常做法是把字段设置为 !),然后引导他们使用找回密码的系统。

8

这是我让事情运转起来的方法。我创建了一个自定义的身份验证后端。注意:我使用电子邮件地址作为用户名。

这是我的代码:

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,所以无法使用。不过,这看起来会是一个更好的解决方案。

撰写回答