如何用Cerberus自定义转换器实现MongoDB敏感数据加解密持久化?
方案完全可行!手把手教你实现
你的思路非常对——Cerberus的自定义转换器确实是处理MongoDB敏感字段加密/解密的理想方案,完全能满足你「仅加密指定字段、其余内容保持原样」的需求。下面我一步步给你讲清楚怎么实现,包括参数传递的细节:
1. 先实现基础的加密解密工具函数
首先得有靠谱的加密逻辑,这里用cryptography库的Fernet对称加密(实际项目里密钥一定要存在安全的地方,比如环境变量,别硬编码):
from cryptography.fernet import Fernet import os # 生成密钥(首次运行后把密钥存到环境变量,下次直接读取) if not os.getenv("ENCRYPTION_KEY"): os.environ["ENCRYPTION_KEY"] = Fernet.generate_key().decode() cipher = Fernet(os.getenv("ENCRYPTION_KEY").encode()) def encrypt_value(value): # 只对字符串类型的敏感值加密,非字符串直接返回 if isinstance(value, str): return cipher.encrypt(value.encode()).decode() return value def decrypt_value(value): # 解密时处理可能的异常(比如字段未加密) if isinstance(value, str): try: return cipher.decrypt(value.encode()).decode() except (Fernet.InvalidToken, TypeError): return value return value
2. 自定义Cerberus转换器类
Cerberus的转换器方法需要遵循它的规范,我们可以把encrypt参数通过字段的Schema配置传递进去,不用给方法加默认值:
from cerberus import BaseNormalizer class EncryptionNormalizer(BaseNormalizer): def _normalize_coerce_encrypt_decrypt(self, value): # 从当前字段的Schema配置中读取encrypt参数 encrypt = self.schema.get("encrypt", True) # 只做加密/解密,不添加任何额外标准化操作 if encrypt: return encrypt_value(value) else: return decrypt_value(value)
3. 在Eve中注册转换器并配置Schema
接下来把自定义转换器注册到Eve,然后在需要加密的字段上指定使用这个转换器,并设置encrypt参数:
from eve import Eve # 定义你的数据模型Schema DOMAIN = { "users": { "schema": { "username": {"type": "string"}, # 针对敏感字段配置转换器和加密参数 "pseudo_password": { "type": "string", "normalizer": "encrypt_decrypt", # 指定用我们的自定义转换器 "encrypt": True # 存储时开启加密 }, "other_field": {"type": "string"} # 这个字段不会被处理,保持原样 } } } # 注册自定义转换器 NORMALIZERS = [EncryptionNormalizer()] # 初始化Eve app app = Eve(domain=DOMAIN)
4. 处理读取时的解密逻辑
因为Cerberus的转换器主要处理输入数据(也就是客户端POST/PUT过来的内容),所以存储时已经自动加密了。但读取数据时需要解密返回给客户端,我们可以用Eve的钩子函数来实现:
def decrypt_sensitive_fields(resource, item): # 只处理指定资源的敏感字段 if resource == "users" and "pseudo_password" in item: item["pseudo_password"] = decrypt_value(item["pseudo_password"]) # 绑定钩子:处理单条数据和批量数据的读取 app.on_fetched_item += decrypt_sensitive_fields app.on_fetched_resource += lambda r, items: [decrypt_sensitive_fields(r, item) for item in items] if __name__ == "__main__": app.run()
关于参数传递的说明
你不用手动调用转换器,Eve会根据Schema配置自动触发:
- 当客户端提交数据(POST/PUT)时,Eve会对
pseudo_password字段调用encrypt_decrypt转换器,读取Schema里的encrypt=True参数,自动加密后存入MongoDB; - 当读取数据时,钩子函数会自动调用解密函数,把加密后的字段还原成明文返回给客户端。
这个方案完全符合你的要求:只加密指定字段,其余文档内容不受任何影响,也没有额外的标准化操作。
内容的提问来源于stack exchange,提问作者Mayday
相关产品推荐
相关产品推荐

