我想使用 Pydantic 使用特定的哈希函数将明文字符串密码转换为分配时字节类型的哈希值。
这是一个最小的示例,显示了我当前(不起作用)的方法。不过我对Pydantic还没有很深入的了解。
import bcrypt
from pydantic import BaseModel, field_validator
def hash_password(password: str) -> bytes:
return bcrypt.hashpw(password.encode('utf-8'), salt = bcrypt.gensalt())
class Password(BaseModel):
hash_value: bytes
@field_validator("hash_value")
@classmethod
def set_password(cls, plain_password: str) -> bytes:
return hash_password(plain_password)
class Settings:
DEFAULT_PASSWORD = "my_plain_password"
settings = Settings()
password_doc = Password(
hash_value = settings.DEFAULT_PASSWORD
)
一开始我不小心将hash_values声明为str,没有意识到
hashpw
的返回值是bytes类型。这以某种方式起作用了,
hash_password
函数在分配时被调用。然而,发生的所有隐式类型转换都使我的散列密码无效。
现在的问题是 Pydantic 在赋值时需要一个字节值,并在将字符串传递给
settings.DEFAULT_PASSWORD
方法之前将字符串
set_password
隐式转换为字节值,即使这个需要字符串类型。
我的错误消息:
Traceback (most recent call last):
File "xxx", line 20, in <module>
password_doc = Password(
^^^^^^^^^
File "xxx", line 176, in __init__
self.__pydantic_validator__.validate_python(data, self_instance=self)
File "xxx", line 13, in set_password
return hash_password(plain_password)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "xxx", line 5, in hash_password
return bcrypt.hashpw(password.encode('utf-8'), salt = bcrypt.gensalt())
^^^^^^^^^^^^^^^
AttributeError: 'bytes' object has no attribute 'encode'. Did you mean: 'decode'?
问题在于正在尝试在已经为字节类型的值上使用
field_validator
。Pydantic 首先将
settings.DEFAULT_PASSWORD
(一个字符串)转换为
hash_value
字段定义的类型(字节)。然后,它尝试对已转换的值应用验证器,从而导致错误,因为正在尝试对字节字符串进行编码。
有两种主要方法可以解决此问题:
1. 使用根验证器:
可以使用根验证器来对整个模型数据进行操作,并在创建实例后设置
hash_value
。
import bcrypt
from pydantic import BaseModel, root_validator
def hash_password(password: str) -> bytes:
return bcrypt.hashpw(password.encode('utf-8'), salt=bcrypt.gensalt())
class Password(BaseModel):
hash_value: bytes
@root_validator(pre=True)
def hash_password(cls, values: dict) -> dict:
if "hash_value" in values and isinstance(values["hash_value"], str):
values["hash_value"] = hash_password(values["hash_value"])
return values
class Settings:
DEFAULT_PASSWORD = "my_plain_password"
settings = Settings()
password_doc = Password(hash_value=settings.DEFAULT_PASSWORD)
print(password_doc)
这种方法使用
root_validator
并在
pre=True
中指定,以便在对字段进行任何其他验证之前运行。验证器检查
hash_value
是否是字符串;如果是,则对其进行哈希处理,然后更新模型中的值。
2. 使用专用字段进行明文密码:
创建单独的字段来处理明文密码,并在模型内部进行哈希处理。
import bcrypt
from pydantic import BaseModel, validator
def hash_password(password: str) -> bytes:
return bcrypt.hashpw(password.encode('utf-8'), salt=bcrypt.gensalt())
class Password(BaseModel):
plain_password: str = None # 可选字段,用于存储明文密码
hash_value: bytes
@validator("hash_value", pre=True, always=True)
def set_hash_from_password(cls, value, values: dict) -> bytes:
if "plain_password" in values and values["plain_password"]:
return hash_password(values["plain_password"])
elif isinstance(value, str):
return hash_password(value)
return value
class Settings:
DEFAULT_PASSWORD = "my_plain_password"
settings = Settings()
password_doc = Password(plain_password=settings.DEFAULT_PASSWORD)
print(password_doc)
此方法使用
plain_password
字段来接收明文密码。
@validator
装饰的
hash_value
字段确保始终使用
plain_password
或直接传递的值生成哈希值。
选择哪种方法取决于的具体需求和偏好。使用根验证器可以提供更大的灵活性来处理更复杂的逻辑,而专用字段提供了一种更清晰的方式来分离明文和哈希密码。
标签:python,python-3.x,validation,pydantic-v2 From: 78773899