将Django密码验证器与Django Rest Framework的validate_password集成

33
我将尝试将 django validators 1.9 与 Django Rest Framework 序列化器集成。但是,序列化的“用户”(来自 Django Rest Framework)与 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,因为这两个函数在验证时都没有使用'user'。源代码在这里

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处注入一个字段
4个回答

60

像你提到的那样,当你使用UserAttributeSimilarityValidator验证器在validate_password方法中验证password时,你没有user对象。

我的建议是,不要进行字段级别的验证,而是通过在序列化器上实现validate方法来执行对象级别的验证

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)

1
你的代码中有几个错别字。顺便说一下,你的解决方案在验证方面运行良好,但是 Django Rest Framework 无法获取字段名称,因此出现了非字段错误 ---> { "non_field_errors": [ "密码与电子邮件太相似。", "此密码太短。它必须至少包含8个字符。" ] } - momokjaaaaa
我实际上还没有测试过这段代码,因此可能会有一些错别字,但这不应该阻止您正确地使用和测试此代码。关于“NON_FIELD_ERRORS”,我已更新我的答案,现在引发的错误应包含password字段名称作为这些错误的键。 - AKS
3
请注意,在进行部分(PATCH)更新时,**data可能不包含创建用户对象所需的所有字段。请翻译完整句子,并保持原意,同时以通俗易懂的方式表达,但不要添加任何额外信息。 - faph
这个答案对我来说立即生效,甚至给了我想要的确切的400 Bad Request响应(唯一的错别字是user=User)。但是在哪里记录了在validate中使用raise serializers.ValidationError会导致400 Bad Request响应?目前,django_rest_framework对我来说有点神秘,因为我找不到这种行为的文档。任何建议都将不胜感激。我想更好地理解它。 - M3RS

17

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

您可以通过序列化器对象上的 self.instance 访问用户对象,即使在执行字段级别验证时也是如此。下面的代码应该可以工作:

 from django.contrib.auth import password_validation

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

12
仅当用户已创建时才能使用此功能,而在注册新用户并在密码创建之前进行验证时并非如此。 - tbm

11
使用序列化器!拥有一个名为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() 都正常工作。 - Elias Prado

0
在创建新用户(注册)时,self.instance将为none,在重置密码、更改密码或使用密码更新用户数据时才会起作用。 但是,如果您想检查密码不应与电子邮件或用户名相似,则需要在验证中包含“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.")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接