将 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 集成的主要内容,如果未能解决你的问题,请参考以下文章