将 django 密码验证器与 django rest 框架 validate_password 集成

Posted

技术标签:

【中文标题】将 django 密码验证器与 django rest 框架 validate_password 集成【英文标题】:integrate django password validators with django rest framework validate_password 【发布时间】:2021-06-17 14:53:15 【问题描述】:

我正在尝试将 django validators 1.9 与 django rest 框架序列化程序集成。但是序列化的“用户”(django rest 框架)与 django 验证器不兼容。

这里是serializers.py

import django.contrib.auth.password_validation as validators
from rest_framework import serializers

    class RegisterUserSerializer(serializers.ModelSerializer):

        password = serializers.CharField(style='input_type': 'password', write_only=True)

        class Meta:
            model = User
            fields = ('id', 'username', 'email, 'password')

        def validate_password(self, data):
            validators.validate_password(password=data, user=User)
            return data

        def create(self, validated_data):
            user = User.objects.create_user(**validated_data)
            user.is_active = False
            user.save()
            return user

我设法使 MinimumLengthValidator 和 NumericPasswordValidator 正确,因为这两个函数 validate 都不使用“用户”进行验证。源代码为here

摘自django源码:

def validate(self, password, user=None):
        if password.isdigit():
            raise ValidationError(
                _("This password is entirely numeric."),
                code='password_entirely_numeric',
            )

对于像 UserAttributeSimilarityValidator 这样的其他验证器,该函数在验证时使用另一个参数 'user'('user' 是 django 用户模型,如果我没记错的话)

摘自django源码:

 def validate(self, password, user=None):
        if not user:
            return

        for attribute_name in self.user_attributes:
            value = getattr(user, attribute_name, None)

如何将序列化的用户更改为 django 验证器(UserAttributeSimilarityValidator)可以看到的内容

摘自django源码:

def validate(self, password, user=None):
        if not user:
            return

        for attribute_name in self.user_attributes:
            value = getattr(user, attribute_name, None)
            if not value or not isinstance(value, string_types):
                continue

编辑

Django Rest Framework 可以获得 Django 的所有内置密码验证(但它就像一个 hack)。这里有一个问题:

validationError是这样的

[ValidationError(['这个密码太短。它必须包含在 最少 8 个字符。']), ValidationError(['这个密码完全是 数字。'])]

验证不包含字段。 Django rest framework 将其视为


    "non_field_errors": [
        "This password is too short. It must contain at least 8 characters.",
        "This password is entirely numeric."
    ]

如何在raise ValidationError 注入字段

【问题讨论】:

【参考方案1】:

正如您所提到的,当您使用 UserAttributeSimilarityValidator 验证器在 validate_password 方法中验证 password 时,您没有 user 对象。

我建议不要进行字段级验证,而应通过在序列化程序上实现 validate 方法来执行 object-level validation:

import sys
from django.core import exceptions
import django.contrib.auth.password_validation as validators

class RegisterUserSerializer(serializers.ModelSerializer):

     # rest of the code

     def validate(self, data):
         # here data has all the fields which have validated values
         # so we can create a User instance out of it
         user = User(**data)

         # get the password from the data
         password = data.get('password')

         errors = dict() 
         try:
             # validate the password and catch the exception
             validators.validate_password(password=password, user=User)

         # the exception raised here is different than serializers.ValidationError
         except exceptions.ValidationError as e:
             errors['password'] = list(e.messages)

         if errors:
             raise serializers.ValidationError(errors)

         return super(RegisterUserSerializer, self).validate(data)

【讨论】:

您的代码中有一些错字。顺便说一句,您的解决方案在验证时工作正常,但 django rest 框架无法获取字段名称,因此 non_field_errors ---> "non_field_errors": [ "The password is too similar to the email.", "This password is太短了。它必须至少包含 8 个字符。” ] 我还没有实际测试过代码,所以这里和那里可能会有一些拼写错误,但这不应该阻止你正确使用和测试这段代码。关于NON_FIELD_ERRORS 我更新了我的答案,现在引发的错误应该包含password 字段名称作为这些错误的关键。 请记住,在进行部分 (PATCH) 更新时,**data 可能不包括创建用户对象所需的所有字段。 即使给出了我想要的确切的 400 Bad Request 响应(唯一的错字是user=User),这个答案也立即对我有用。但是在哪里记录 raise serializers.ValidationError 中的 validate 会导致 400 Bad Request 响应? django_rest_framework 目前对我来说有点神秘,因为我找不到这种行为的文档。任何建议表示赞赏。我想更好地理解。【参考方案2】:

您可以通过序列化程序对象上的self.instance 访问用户对象,即使在进行字段级验证时也是如此。像这样的东西应该可以工作:

 from django.contrib.auth import password_validation

 def validate_password(self, value):
    password_validation.validate_password(value, self.instance)
    return value

【讨论】:

只有在用户已经创建时才可用,在注册新用户和验证密码预创建时不会出现这种情况。【参考方案3】:

使用序列化器!有一个validate_fieldname 方法!

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = (
            'id', 'username', 'password', 'first_name', 'last_name', 'email'
        )
        extra_kwargs = 
            'password': 'write_only': True,
            'username': 'read_only': True
        

    def validate_password(self, value):
        try:
            validate_password(value)
        except ValidationError as exc:
            raise serializers.ValidationError(str(exc))
        return value

    def create(self, validated_data):
        user = super().create(validated_data)
        user.set_password(validated_data['password'])

        user.is_active = False
        user.save()
        return user

    def update(self, instance, validated_data):
        user = super().update(instance, validated_data)
        if 'password' in validated_data:
            user.set_password(validated_data['password'])
            user.save()
        return user

【讨论】:

只有validate_data 不起作用。 create() 和 update() 工作正常。【参考方案4】:

在创建新用户(注册)时 self.instance 将是无,它会在什么时候起作用 您正在重置密码、更改密码或使用密码更新用户数据。 但是如果您想检查密码不应该与您的电子邮件或用户名相似,那么您需要包括“SequenceMatcher” 在您的验证中

data = self.get_initial()
username = data.get("username")
email = data.get("email")
password = data.get("password") 
max_similarity = 0.7
if SequenceMatcher(a=password.lower(), b=username.lower()).quick_ratio() > max_similarity:
    raise serializers.ValidationError("The password is too similar to the username.")
if SequenceMatcher(a=password.lower(), b=email.lower()).quick_ratio() > max_similarity:
    raise serializers.ValidationError("The password is too similar to the email.")

【讨论】:

以上是关于将 django 密码验证器与 django rest 框架 validate_password 集成的主要内容,如果未能解决你的问题,请参考以下文章

django之账号密码验证登陆

Django 表单:无法显示验证错误

Django-检查两个密码哈希是否具有相同的原始密码

Django用户注册验证不起作用

django rest framework jwt 使用电子邮件和密码进行身份验证

没有密码的django身份验证