首页 > 解决方案 > 无法登录,因为 Django 以纯文本格式保存密码,但是

问题描述

class UserManager(BaseUserManager):

    def create_user(self, username, email, password=None):
        if username is None:
            raise TypeError('User should have a username')
        if email is None:
            raise TypeError('User should have an email')

        user = self.model(
            username=username,
            email=self.normalize_email(email)
        )
        user.set_password(password)
        user.save(using=self._db)
        return user


    def create_superuser(self, username, email, password=None):
        if password is None:
            raise TypeError('Password should not be none')

        user = self.create_user(username, email, password)
        user.is_superuser = True
        user.is_staff = True
        user.save()
        return user



class User(AbstractBaseUser, PermissionsMixin):
    
    username = models.CharField(
        max_length = 255,
        unique = True,
        db_index = True
    )
    email = models.EmailField(
        max_length = 255,
        unique = True,
        db_index = True
    )
    is_verified = models.BooleanField(default = False)
    is_staff = models.BooleanField(default = True)
    is_active = models.BooleanField(default = True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    objects = UserManager()

    def __str__(self):
        return self.email

    def tokens(self):
        refresh = RefreshToken.for_user(self)
        return {
            'refresh': str(refresh),
            'access': str(refresh.access_token)
        }


class RegisterSerializer(serializers.ModelSerializer):

    
    password = serializers.CharField(
        max_length = 255,
        min_length = 6,
        write_only = True
    )

    class Meta:
        model = User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True, 'min_length': 5}}

        def validate(self, attrs):
            email = attrs.get('email', '')
            username = attrs.get('username', '')

            if not username.isalnum():
                raise serializers.ValidationError(
                    "Username should contain only alphanumeric characters"
                )
            return attrs


        def create(self, validated_data):
            return User.objects.create_user(**validated_data)



class RegisterView(generics.GenericAPIView):

    permission_classes = (AllowAny,)
    serializer_class = RegisterSerializer

    def post(self, request):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        user_data = serializer.data

        return Response(
            serializer.data,
            status=status.HTTP_201_CREATED
        )

目前我正在使用django 3.1.3and djangorestframework 3.12.2。虽然我能够使用超级用户登录并正确获取令牌,但无法使用员工用户登录。从 Django admin 我看到只有超级用户有散列密码,所有其他用户都有纯文本密码。

标签: djangodjango-rest-framework

解决方案


您不使用 设置密码user.password,这只是模型上的一个文本字段,并且可以像其他任何字段一样工作。您必须:

  1. 首选:致电user.set_password(value)
  2. 备份make_password:在保存之前手动计算密码

你已经有了一个create方法,所以我们将首先使用它。

from django.contrib.auth.hashers import make_password

def create(self, validated_data):
    pwd = validated_data.pop("password")
    user = User.objects.create(**attrs)
    user.set_password(pwd)
    user.save(updated_fields=["password"])
    return user

# or you could replace it in validated_data
def create(self, validated_data):
    validated_data["password"] = make_password(validated_data["password"])
    return User.objects.create(**attrs)

您有一个验证例程,但它是全局的。如果您将其设置为特定于密码字段,那么您可以在那里计算它而无需create理会。人们通常不会这样做,因为“确认密码”字段也是标准的。

def validate_password(self, value):
    # ...
    return make_password(value)

现在,如果您想添加一个confirm_password字段,那么无论如何您都需要validate(self, attrs)比较这两个字段,所以我建议不要使用最后一种方法。


推荐阅读