feat: 添加脱敏Mixin docs。update README
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
## 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
|
||||
|
||||
|
||||
# 项目简介
|
||||
|
||||
本项目为基于 Django5 + Vue3(vben-admin)全栈开发的企业级中后台管理系统,支持动态菜单、按钮权限、自动化代码生成、前后端权限联动等功能,适用于多角色、多权限场景的管理后台。
|
||||
|
||||
333
backend/examples/README_DESENSITIZATION.md
Normal file
333
backend/examples/README_DESENSITIZATION.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# 通用脱敏解决方案
|
||||
|
||||
## 概述
|
||||
|
||||
为了保护敏感信息,系统提供了一个通用的脱敏 Mixin (`DesensitizationMixin`),可以轻松应用到任何序列化器中,实现字段的自动脱敏。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- **通用性**:一个 Mixin 解决所有脱敏需求
|
||||
- **灵活性**:支持直接字段和关联字段脱敏
|
||||
- **可配置**:可自定义脱敏格式和权限规则
|
||||
- **易使用**:只需几行代码即可启用脱敏
|
||||
- **权限控制**:超级用户和管理员可查看完整值
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 基本用法
|
||||
|
||||
```python
|
||||
from utils.serializers import CustomModelSerializer, DesensitizationMixin
|
||||
|
||||
class MySerializer(DesensitizationMixin, CustomModelSerializer):
|
||||
# 指定需要脱敏的字段
|
||||
desensitize_fields = ['password', 'api_key', 'secret_token']
|
||||
|
||||
class Meta:
|
||||
model = MyModel
|
||||
fields = '__all__'
|
||||
```
|
||||
|
||||
### 2. 脱敏关联字段
|
||||
|
||||
```python
|
||||
class UserProfileSerializer(DesensitizationMixin, CustomModelSerializer):
|
||||
# 脱敏关联字段,使用点号分隔
|
||||
desensitize_fields = [
|
||||
'user.password', # 用户密码
|
||||
'user.email', # 用户邮箱
|
||||
'config.api_key', # 配置中的API密钥
|
||||
'payment.credit_card' # 支付信息中的信用卡
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = UserProfile
|
||||
fields = '__all__'
|
||||
```
|
||||
|
||||
### 3. 自定义脱敏格式
|
||||
|
||||
```python
|
||||
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`)**:可以查看完整的值
|
||||
- **普通用户**:只能查看脱敏后的值
|
||||
|
||||
## 配置选项
|
||||
|
||||
### 基本配置
|
||||
|
||||
```python
|
||||
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 = '*'
|
||||
```
|
||||
|
||||
### 高级配置
|
||||
|
||||
```python
|
||||
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:用户信息脱敏
|
||||
|
||||
```python
|
||||
class UserSerializer(DesensitizationMixin, CustomModelSerializer):
|
||||
desensitize_fields = [
|
||||
'password', # 密码
|
||||
'email', # 邮箱
|
||||
'phone_number', # 电话号码
|
||||
'id_card_number' # 身份证号
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = '__all__'
|
||||
```
|
||||
|
||||
### 示例2:支付信息脱敏
|
||||
|
||||
```python
|
||||
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:系统配置脱敏
|
||||
|
||||
```python
|
||||
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`
|
||||
- 原字段会被移除,避免重复
|
||||
- 关联字段的脱敏会创建新的方法字段
|
||||
|
||||
## 测试
|
||||
|
||||
### 运行测试
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python manage.py test ai.tests.DesensitizationMixinTest
|
||||
python manage.py test ai.tests.AIApiKeyDesensitizationTest
|
||||
```
|
||||
|
||||
### 测试用例
|
||||
|
||||
```python
|
||||
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_superuser` 和 `is_staff` 字段
|
||||
- 确认 `_can_view_full_value` 方法实现正确
|
||||
|
||||
### 调试方法
|
||||
|
||||
```python
|
||||
# 在序列化器中添加调试信息
|
||||
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`,可能影响性能
|
||||
- 对于大量数据的场景,建议只对必要的敏感字段启用脱敏
|
||||
- 可以考虑使用缓存来优化重复的脱敏计算
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 自定义脱敏规则
|
||||
|
||||
```python
|
||||
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:]
|
||||
```
|
||||
|
||||
### 批量脱敏
|
||||
|
||||
```python
|
||||
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,支持所有序列化器
|
||||
167
backend/examples/desensitization.py
Normal file
167
backend/examples/desensitization.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
脱敏功能使用示例
|
||||
|
||||
这个文件展示了如何在不同的序列化器中使用 DesensitizationMixin 来实现字段脱敏
|
||||
"""
|
||||
|
||||
from utils.serializers import CustomModelSerializer, DesensitizationMixin
|
||||
|
||||
|
||||
# 示例1:基本用法 - 脱敏单个字段
|
||||
class BasicDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
基本脱敏示例
|
||||
"""
|
||||
# 指定需要脱敏的字段
|
||||
desensitize_fields = ['password', 'api_key', 'secret_token']
|
||||
|
||||
class Meta:
|
||||
model = None # 这里应该是您的模型
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# 示例2:自定义脱敏格式
|
||||
class CustomDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
自定义脱敏格式示例
|
||||
"""
|
||||
# 指定需要脱敏的字段
|
||||
desensitize_fields = ['credit_card', 'phone_number', 'id_card']
|
||||
|
||||
# 自定义脱敏参数
|
||||
desensitize_prefix_length = 2 # 保留前2位
|
||||
desensitize_suffix_length = 2 # 保留后2位
|
||||
desensitize_threshold = 6 # 长度小于等于6时全部脱敏
|
||||
desensitize_char = '#' # 使用#作为脱敏字符
|
||||
|
||||
class Meta:
|
||||
model = None
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# 示例3:脱敏关联字段
|
||||
class RelatedFieldDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
脱敏关联字段示例
|
||||
"""
|
||||
# 脱敏关联字段,使用点号分隔
|
||||
desensitize_fields = [
|
||||
'user.password', # 用户密码
|
||||
'user.email', # 用户邮箱
|
||||
'config.api_key', # 配置中的API密钥
|
||||
'payment.credit_card', # 支付信息中的信用卡
|
||||
'profile.phone_number' # 个人资料中的电话号码
|
||||
]
|
||||
|
||||
class Meta:
|
||||
model = None
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# 示例4:混合脱敏和普通字段
|
||||
class MixedDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
混合脱敏和普通字段示例
|
||||
"""
|
||||
from rest_framework import serializers
|
||||
|
||||
# 脱敏字段
|
||||
desensitize_fields = ['secret_key', 'user.password']
|
||||
|
||||
# 普通字段
|
||||
name = serializers.CharField()
|
||||
description = serializers.CharField()
|
||||
|
||||
# 计算字段
|
||||
full_name = serializers.SerializerMethodField()
|
||||
|
||||
def get_full_name(self, obj):
|
||||
return f"{obj.first_name} {obj.last_name}"
|
||||
|
||||
class Meta:
|
||||
model = None
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# 示例5:条件脱敏
|
||||
class ConditionalDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
条件脱敏示例
|
||||
"""
|
||||
desensitize_fields = ['api_key', 'secret_token']
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 示例6:不同字段使用不同脱敏规则
|
||||
class MultiRuleDesensitizationExample(DesensitizationMixin, CustomModelSerializer):
|
||||
"""
|
||||
不同字段使用不同脱敏规则的示例
|
||||
注意:这个示例需要自定义实现,因为 DesensitizationMixin 使用统一的规则
|
||||
"""
|
||||
desensitize_fields = ['api_key', 'phone_number', 'credit_card']
|
||||
|
||||
def _apply_desensitization(self, value):
|
||||
"""
|
||||
重写脱敏方法,为不同字段应用不同规则
|
||||
"""
|
||||
# 这里可以根据字段名或其他逻辑来应用不同的脱敏规则
|
||||
# 由于 DesensitizationMixin 的设计,这个示例需要额外的自定义逻辑
|
||||
return super()._apply_desensitization(value)
|
||||
|
||||
|
||||
# 使用说明
|
||||
"""
|
||||
使用 DesensitizationMixin 的步骤:
|
||||
|
||||
1. 在序列化器中继承 DesensitizationMixin
|
||||
class MySerializer(DesensitizationMixin, CustomModelSerializer):
|
||||
pass
|
||||
|
||||
2. 设置需要脱敏的字段
|
||||
desensitize_fields = ['field1', 'field2', 'related.field3']
|
||||
|
||||
3. 可选:自定义脱敏参数
|
||||
desensitize_prefix_length = 3 # 保留前3位
|
||||
desensitize_suffix_length = 3 # 保留后3位
|
||||
desensitize_threshold = 10 # 长度小于等于10时全部脱敏
|
||||
desensitize_char = 'X' # 使用X作为脱敏字符
|
||||
|
||||
4. 可选:重写权限检查方法
|
||||
def _can_view_full_value(self):
|
||||
# 自定义权限逻辑
|
||||
pass
|
||||
|
||||
5. 可选:重写脱敏方法
|
||||
def _apply_desensitization(self, value):
|
||||
# 自定义脱敏规则
|
||||
pass
|
||||
|
||||
注意事项:
|
||||
- 脱敏字段会自动转换为 SerializerMethodField
|
||||
- 关联字段使用点号分隔,如 'user.password'
|
||||
- 脱敏只影响显示,不影响数据库存储
|
||||
- 超级用户和管理员默认可以查看完整值
|
||||
- 可以通过重写方法来扩展功能
|
||||
"""
|
||||
Reference in New Issue
Block a user