Files
django-vue3-admin-gd/backend/examples/README_DESENSITIZATION.md
2025-09-01 10:34:10 +08:00

9.8 KiB
Raw Permalink Blame History

通用脱敏解决方案

概述

为了保护敏感信息,系统提供了一个通用的脱敏 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-1234567890abcdefghijklmnopqrstuvwxyzsk-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)

故障排除

常见问题

  1. 脱敏不生效

    • 检查是否正确继承了 DesensitizationMixin
    • 确认 desensitize_fields 配置正确
    • 验证序列化器是否正确设置了 context
  2. 关联字段脱敏失败

    • 检查关联字段路径是否正确(使用点号分隔)
    • 确认关联对象存在且可访问
    • 验证字段名拼写正确
  3. 权限检查失败

    • 检查用户是否已登录
    • 验证 is_superuseris_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支持所有序列化器