【问题标题】:Why isn't my Django User Model's Password Hashed?为什么我的 Django 用户模型的密码没有经过哈希处理?
【发布时间】:2015-02-19 13:44:52
【问题描述】:

我正在使用 Django REST 框架 (DRF) 创建一个可以注册新用户的端点。但是,当我使用 POST 访问创建端点时,新用户将通过序列化程序保存,但密码以明文形式保存在数据库中。我的序列化器的代码如下:

from django.contrib.auth import get_user_model
from rest_framework import serializers

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = get_user_model()
        fields = ['password', 'username', 'first_name', 'last_name', 'email']
        read_only_fields = ['is_staff', 'is_superuser']
        write_only_fields = ['password']

请注意,我使用的是 Django auth 包中的默认用户模型,而且我对使用 DRF 非常陌生!此外,我发现 this question 提供了一个解决方案,但这似乎需要两次数据库交互——我不认为这是有效的,但这可能是我的错误假设。

【问题讨论】:

标签: python django django-rest-framework


【解决方案1】:

这是已接受答案的替代方法。

class CreateUserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('email', 'username', 'password')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User.objects.create_user(
            email=validated_data['email'],
            username=validated_data['username'],
            password=validated_data['password'],
        )
        user.save()
        return user

create_user 函数在UserManager 中定义,它使用set_password(),我们不需要显式使用它。我找到了许多建议使用set_password 的答案和文章,但是在尝试了很多事情之后,我发现上面的内容也适用于CustomUserManager。 假设注册用户需要电话号码和密码。所以我们的CustomUserManager 看起来像这样,CreateUserSerializer 也会处理这个问题而不会做任何更改。


class CustomUserManager(BaseUserManager):

    def create_user(self, phone_number, password):
        if not phone_number:
            raise ValueError('Phone Number must be set')

        user = self.model(phone_number=phone_number)
        user.set_password(password)
        user.save(using=self._db)
        return user

【讨论】:

    【解决方案2】:

    这对我有用。

    class UserSerializer(serializers.ModelSerializer):
        def create(self, *args, **kwargs):
            user = super().create(*args, **kwargs)
            p = user.password
            user.set_password(p)
            user.save()
            return user
    
        def update(self, *args, **kwargs):
            user = super().update(*args, **kwargs)
            p = user.password
            user.set_password(p)
            user.save()
            return user
    
        class Meta:
            model = get_user_model()
            fields = "__all__" 
    

    【讨论】:

      【解决方案3】:

      问题是 DRF 只会将字段值设置到模型上。因此,密码设置在密码字段上,并保存在数据库中。但要正确设置密码,您需要调用 set_password() 方法,该方法将进行哈希处理。

      有几种方法可以做到这一点,但在 rest framework v3 上最好的方法是覆盖序列化器上的 update()create() 方法。

      class UserSerializer(serializers.ModelSerializer):
          # <Your other UserSerializer stuff here>
      
          def create(self, validated_data):
              password = validated_data.pop('password', None)
              instance = self.Meta.model(**validated_data)
              if password is not None:
                  instance.set_password(password)
              instance.save()
              return instance
      
          def update(self, instance, validated_data):
              for attr, value in validated_data.items():
                  if attr == 'password':
                      instance.set_password(value)
                  else:
                      setattr(instance, attr, value)
              instance.save()
              return instance
      

      这里有两件事:

      1. 我们用户self.Meta.model,所以如果模型在 序列化程序,它仍然可以工作(只要它有一个set_password 当然是方法)。
      2. 我们迭代 validated_data 项目而不是 字段,以说明可选的 excludeed 字段。

      另外,这个版本的create 不保存M2M 关系。您的示例中不需要,但如果需要,可以添加。您需要从字典中弹出它们,保存模型并在之后设置它们。

      FWIW,我因此将这个答案中的所有 python 代码都设为全球公共领域。它的分发没有任何保证。

      【讨论】:

      • 很好的答案,但我对此有点陌生,所以我不得不问你不保存模型到模型关系是什么意思。如果我使用ModelSerializer,这个功能不会自动出现吗?
      • 对于model2model关系,我的意思是如果你保存连接到用户的其他模型,使用嵌套序列化程序,我提供的示例只会保存用户,而不是其他对象。至于您的链接,它是关于 REST Framework v2 的。版本 3 更改了有关模型保存的 API。
      • 万一新人遇到同样的问题,原始答案没有说明需要在哪里添加这些方法。我试图将这些方法添加到视图中,但实际上它应该添加到模型的序列化程序中。可能从方法签名中可以明显看出,但对于新手来说,可能并不那么明显。
      • @DivKis01> 进行了编辑,以明确序列化程序上的方法。谢谢。
      • @bgarcial> 我不知道,但你在打开 another question 时做了正确的事情,我很高兴有人帮助你解决了你的问题。
      【解决方案4】:

      只需覆盖序列化程序的create and update methods

         def create(self, validated_data):
              user = get_user_model(**validated_data)
              user.set_password(validated_data['password'])
              user.save()
              return user
      
          def update(self, instance, validated_data):
              for f in UserSerializer.Meta.fields + UserSerializer.Meta.write_only_fields:
                  set_attr(instance, f, validated_data[f])
              instance.set_password(validated_data['password'])
              instance.save()
              return instance
      

      【讨论】:

      • 您的方法与下面答案的方法之间是否存在显着差异?他们看起来有点像我
      • @nmagerko 不,方法是完全一样的,我不检查密码是否可以是无,我更喜欢简洁地概述方法并留给你实际的实现,如果你关心的话请改变它!
      • 不幸的是,我必须接受另一个答案;在这里使用get_user_model 并不总是有效,因为验证数据中提供的某些关键字是无关的,并且会引发错误:(
      • @nmagerko 我也更喜欢这种方法!
      猜你喜欢
      • 2018-11-26
      • 2021-01-25
      • 1970-01-01
      • 1970-01-01
      • 2015-12-30
      • 1970-01-01
      • 2022-08-17
      • 2012-01-08
      • 1970-01-01
      相关资源
      最近更新 更多