9.8 KiB
9.8 KiB
通用脱敏解决方案
概述
为了保护敏感信息,系统提供了一个通用的脱敏 Mixin (DesensitizationMixin),可以轻松应用到任何序列化器中,实现字段的自动脱敏。
核心特性
- 通用性:一个 Mixin 解决所有脱敏需求
- 灵活性:支持直接字段和关联字段脱敏
- 可配置:可自定义脱敏格式和权限规则
- 易使用:只需几行代码即可启用脱敏
- 权限控制:超级用户和管理员可查看完整值
快速开始
1. 基本用法
from utils.serializers import CustomModelSerializer, DesensitizationMixin
class MySerializer(DesensitizationMixin, CustomModelSerializer):
# 指定需要脱敏的字段
desensitize_fields = ['password', 'api_key', 'secret_token']
class Meta:
model = MyModel
fields = '__all__'
2. 脱敏关联字段
class UserProfileSerializer(DesensitizationMixin, CustomModelSerializer):
# 脱敏关联字段,使用点号分隔
desensitize_fields = [
'user.password', # 用户密码
'user.email', # 用户邮箱
'config.api_key', # 配置中的API密钥
'payment.credit_card' # 支付信息中的信用卡
]
class Meta:
model = UserProfile
fields = '__all__'
3. 自定义脱敏格式
class CustomSerializer(DesensitizationMixin, CustomModelSerializer):
desensitize_fields = ['credit_card', 'phone_number']
# 自定义脱敏参数
desensitize_prefix_length = 2 # 保留前2位
desensitize_suffix_length = 2 # 保留后2位
desensitize_threshold = 6 # 长度小于等于6时全部脱敏
desensitize_char = '#' # 使用#作为脱敏字符
class Meta:
model = MyModel
fields = '__all__'
脱敏规则
默认脱敏格式
- 长度 ≤ 8 的字段:全部用
*替换- 例如:
sk-123→*****
- 例如:
- 长度 > 8 的字段:显示前4位和后4位,中间用
*替换- 例如:
sk-1234567890abcdefghijklmnopqrstuvwxyz→sk-12****************************wxyz
- 例如:
权限控制
- 超级用户 (
is_superuser=True):可以查看完整的值 - 管理员用户 (
is_staff=True):可以查看完整的值 - 普通用户:只能查看脱敏后的值
配置选项
基本配置
class MySerializer(DesensitizationMixin, CustomModelSerializer):
# 需要脱敏的字段列表
desensitize_fields = ['field1', 'field2', 'related.field3']
# 脱敏时保留的前缀长度,默认4
desensitize_prefix_length = 4
# 脱敏时保留的后缀长度,默认4
desensitize_suffix_length = 4
# 脱敏阈值,字段长度小于等于此值时全部脱敏,默认8
desensitize_threshold = 8
# 脱敏字符,默认使用*
desensitize_char = '*'
高级配置
class AdvancedSerializer(DesensitizationMixin, CustomModelSerializer):
desensitize_fields = ['secret_key', 'user.password']
def _can_view_full_value(self):
"""重写权限检查方法,实现自定义权限逻辑"""
request = self.context.get('request')
if not request or not request.user:
return False
# 检查特定权限
if request.user.has_perm('app.view_sensitive_data'):
return True
# 检查角色
if request.user.role.filter(name='数据管理员').exists():
return True
# 检查用户组
if request.user.groups.filter(name='高级用户').exists():
return True
# 默认只有超级用户和管理员可以查看
return request.user.is_superuser or request.user.is_staff
def _apply_desensitization(self, value):
"""重写脱敏方法,实现自定义脱敏规则"""
# 自定义脱敏逻辑
if len(value) <= 6:
return '#' * len(value)
else:
return value[:2] + '#' * (len(value) - 4) + value[-2:]
实际应用示例
示例1:用户信息脱敏
class UserSerializer(DesensitizationMixin, CustomModelSerializer):
desensitize_fields = [
'password', # 密码
'email', # 邮箱
'phone_number', # 电话号码
'id_card_number' # 身份证号
]
class Meta:
model = User
fields = '__all__'
示例2:支付信息脱敏
class PaymentSerializer(DesensitizationMixin, CustomModelSerializer):
desensitize_fields = [
'credit_card_number', # 信用卡号
'cvv', # CVV码
'user.password', # 用户密码
'user.email' # 用户邮箱
]
# 为信用卡号使用更严格的脱敏
desensitize_prefix_length = 2
desensitize_suffix_length = 2
desensitize_threshold = 10
class Meta:
model = Payment
fields = '__all__'
示例3:系统配置脱敏
class SystemConfigSerializer(DesensitizationMixin, CustomModelSerializer):
desensitize_fields = [
'database_password', # 数据库密码
'redis_password', # Redis密码
'api_keys.openai_key', # OpenAI API密钥
'api_keys.aws_secret' # AWS密钥
]
class Meta:
model = SystemConfig
fields = '__all__'
字段映射规则
直接字段
- 字段名:
password→ 序列化后字段名:password
关联字段
- 字段名:
user.password→ 序列化后字段名:user_password - 字段名:
config.api_key→ 序列化后字段名:config_api_key
注意事项
- 脱敏字段会自动转换为
SerializerMethodField - 原字段会被移除,避免重复
- 关联字段的脱敏会创建新的方法字段
测试
运行测试
cd backend
python manage.py test ai.tests.DesensitizationMixinTest
python manage.py test ai.tests.AIApiKeyDesensitizationTest
测试用例
def test_desensitization_mixin_inheritance(self):
"""测试脱敏 Mixin 是否正确继承"""
serializer = AIApiKeySerializer()
# 验证序列化器有脱敏相关的方法
self.assertTrue(hasattr(serializer, '_desensitize_field'))
self.assertTrue(hasattr(serializer, '_can_view_full_value'))
self.assertTrue(hasattr(serializer, '_apply_desensitization'))
def test_desensitization_fields_configuration(self):
"""测试脱敏字段配置"""
serializer = AIApiKeySerializer()
# 验证脱敏字段配置
self.assertIn('api_key', serializer.desensitize_fields)
self.assertEqual(serializer.desensitize_prefix_length, 4)
self.assertEqual(serializer.desensitize_suffix_length, 4)
故障排除
常见问题
-
脱敏不生效
- 检查是否正确继承了
DesensitizationMixin - 确认
desensitize_fields配置正确 - 验证序列化器是否正确设置了
context
- 检查是否正确继承了
-
关联字段脱敏失败
- 检查关联字段路径是否正确(使用点号分隔)
- 确认关联对象存在且可访问
- 验证字段名拼写正确
-
权限检查失败
- 检查用户是否已登录
- 验证
is_superuser和is_staff字段 - 确认
_can_view_full_value方法实现正确
调试方法
# 在序列化器中添加调试信息
def _desensitize_field(self, obj, field_name):
"""脱敏指定字段"""
print(f"脱敏字段: {field_name}")
print(f"对象: {obj}")
# 继续原有的脱敏逻辑
return super()._desensitize_field(obj, field_name)
def _can_view_full_value(self):
"""检查当前用户是否可以查看完整值"""
request = self.context.get('request')
print(f"Request: {request}")
print(f"User: {request.user if request else 'None'}")
# 继续原有的权限检查逻辑
return super()._can_view_full_value()
性能考虑
- 脱敏字段会转换为
SerializerMethodField,可能影响性能 - 对于大量数据的场景,建议只对必要的敏感字段启用脱敏
- 可以考虑使用缓存来优化重复的脱敏计算
扩展功能
自定义脱敏规则
class CustomDesensitizationMixin(DesensitizationMixin):
"""自定义脱敏 Mixin"""
def _apply_desensitization(self, value):
"""实现自定义脱敏规则"""
# 根据字段类型应用不同的脱敏规则
if self._is_credit_card(value):
return self._desensitize_credit_card(value)
elif self._is_phone_number(value):
return self._desensitize_phone_number(value)
else:
return super()._apply_desensitization(value)
def _is_credit_card(self, value):
"""判断是否为信用卡号"""
return len(value) == 16 and value.isdigit()
def _desensitize_credit_card(self, value):
"""信用卡号脱敏:前4位 + 8个* + 后4位"""
return value[:4] + '*' * 8 + value[-4:]
批量脱敏
class BatchDesensitizationMixin(DesensitizationMixin):
"""批量脱敏 Mixin"""
def _batch_desensitize(self, obj_list):
"""批量脱敏对象列表"""
for obj in obj_list:
for field_name in self.desensitize_fields:
if hasattr(obj, field_name):
setattr(obj, f'_{field_name}_desensitized',
self._desensitize_field(obj, field_name))
return obj_list
更新日志
- v1.0.0:初始实现,支持基本字段脱敏
- v1.1.0:添加关联字段脱敏支持
- v1.2.0:支持自定义脱敏参数和权限规则
- v1.3.0:添加完整的测试用例和文档
- v2.0.0:重构为通用 Mixin,支持所有序列化器