知识 - 数据脱敏
# 什么是数据脱敏
数据脱敏(Data Masking)是一种信息安全技术,旨在保护敏感信息和隐私数据,防止未经授权的访问或泄露。它通过对原始数据进行有策略的修改或替换,创建一个看上去与原数据相似但不含真正敏感细节的数据副本,以供非生产环境如开发、测试、分析或培训等用途中安全使用。
数据脱敏的目的:
保护隐私:确保个人信息如身份证号、电话号码、银行账号等不被非法获取和利用
合规要求:满足行业规范和法律法规对数据保护的要求,如GDPR(欧盟通用数据保护条例)等
安全测试:在不影响真实数据安全的前提下,为软件测试、系统调试提供接近真实的测试数据
降低风险:即便数据被非法访问,由于已脱敏,实际敏感信息不会泄露,降低了数据泄露的风险
数据脱敏的应用在生活中是比较常见的,比如我们在淘宝买东西订单详情中,商家账户信息会被用 *
遮挡,保障了商户隐私不泄露,这就是一种数据脱敏方式。
数据脱敏又分为静态数据脱敏(SDM
)和 动态数据脱敏(DDM
):
静态数据脱敏与动态数据脱敏:
静态数据脱敏:在数据被提取并复制到非生产环境之前一次性完成脱敏处理。适用于数据外发场景,如提供给第三方或用于测试数据库
动态数据脱敏:在数据查询过程中实时进行,当用户访问敏感数据时,系统自动对其进行脱敏处理。适用于直接连接生产数据库的场景,确保即使查看数据的行为也不会暴露敏感信息
# 实现数据脱敏
实现数据脱敏步骤:
- 提供 Sensitive 注解,在实体类的属性上使用,注解里可以指定脱敏的类型
- 自定义类继承和实现 jackson 提供的
JsonSerializer
、ContextualSerializer
,这样在返回数据时,会自动触发自定义类的指定方法,将带有 Sensitive
注解的属性进行自动传入(Sensitive 注解使用了 JsonSerialize 的 use 实现自动传入)
脱敏支持的类型请看 SensitiveStrategy
枚举类。主要利用了 hutool 的脱敏方法。
模块支持自定义脱敏类型,实现 SensitiveService
接口,主要基于用户权限来决定是否脱敏并返回。
依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
# 注解
数据脱敏注解,标注属性后,会创建一个 SensitiveHandler 的序列化器
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveHandler.class)
public @interface Sensitive {
/**
* 脱敏规则
*/
SensitiveStrategy strategy();
/**
* UAC 角色码
*/
String roleCode() default "";
/**
* UAC 菜单权限码
*/
String perms() default "";
/**
* 前置不需要打码的长度,仅 strategy 为 SensitiveStrategy.CUSTOMIZE_RULE 生效
*/
int startLen() default 0;
/**
* 后置不需要打码的长度,仅 strategy 为 SensitiveStrategy.CUSTOMIZE_RULE 生效
*/
int endLen() default 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 脱敏策略
脱敏策略,采用 hutool 的 DesensitizedUtil 工具类实现脱敏
@AllArgsConstructor
@Getter
public enum SensitiveStrategy {
/**
* 默认脱敏
*/
DEFAULT(value -> value),
/**
* 自定义规则脱敏
*/
CUSTOMIZE_RULE(value -> value),
/**
* 中文名脱敏
*/
CHINESE_NAME(DesensitizedUtil::chineseName),
/**
* 密码脱敏
*/
PASSWORD(DesensitizedUtil::password),
/**
* 身份证脱敏
*/
ID_CARD(s -> DesensitizedUtil.idCardNum(s, 3, 4)),
/**
* 手机号脱敏
*/
PHONE(DesensitizedUtil::mobilePhone),
/**
* 地址脱敏
*/
ADDRESS(s -> DesensitizedUtil.address(s, 8)),
/**
* 邮箱脱敏
*/
EMAIL(DesensitizedUtil::email),
/**
* 银行卡
*/
BANK_CARD(DesensitizedUtil::bankCard);
private final Function<String, String> desensitize;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 脱敏处理器
实现 ContextualSerializer 初始化一个序列化器,继承 JsonSerializer 实现序列化功能。Spring MVC 返回给前端的时候,会调用继承 JsonSerializer 的自定义类处理。
@Slf4j
public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
private SensitiveStrategy strategy;
private int startLen;
private int endLen;
private String roleCode;
private String perms;
/**
* 执行序列化操作
*/
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
try {
// 默认规则
if (SensitiveStrategy.DEFAULT.equals(strategy)) {
gen.writeString(value);
return;
}
// 自定义规则
if (SensitiveStrategy.CUSTOMIZE_RULE.equals(strategy)) {
gen.writeString(StrUtil.hide(value, startLen, endLen));
return;
}
// 如果传入了 roleCode 和 perms,代表走自定义 Service 实现类来进行校验,返回 true 开启脱敏
SensitiveService sensitiveService = SpringHelper.getBean(SensitiveService.class);
if (StringUtil.hasAnyText(roleCode, perms) && Objects.nonNull(sensitiveService)) {
if (sensitiveService.isSensitive(roleCode, perms)) {
gen.writeString(strategy.getDesensitize().apply(value));
} else {
// 返回 false 不序列化
gen.writeString(value);
}
} else {
gen.writeString(strategy.getDesensitize().apply(value));
}
} catch (BeansException e) {
log.error("脱敏实现不存在, 采用默认处理 => {}", e.getMessage());
gen.writeString(value);
}
}
/**
* 项目初始化后,获取注解信息进行存储,每一个注解对应一个类实例
*/
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
Sensitive annotation = property.getAnnotation(Sensitive.class);
if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) {
this.strategy = annotation.strategy();
this.startLen = annotation.startLen();
this.endLen = annotation.endLen();
this.roleCode = annotation.roleCode();
this.perms = annotation.perms();
return this;
}
return prov.findValueSerializer(property.getType(), property);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 自定义脱敏服务
public interface SensitiveService {
/**
* 是否脱敏
*/
boolean isSensitive(String roleKey, String perms);
}
2
3
4
5
6
# 示例
@RestController
@RequestMapping("/demo/sensitive")
public class DemoSensitiveController {
@RequestMapping("/test")
public Response<TestSensitive> test() {
TestSensitive testSensitive = new TestSensitive();
testSensitive.setMessage("端午节放假啦");
testSensitive.setRuleMessage("123456789");
testSensitive.setIdCard("210397198608215431");
testSensitive.setPhone("15777815847");
testSensitive.setAddress("深圳市龙岗区某某小区1327室");
testSensitive.setEmail("2456019588@qq.com");
testSensitive.setBankCard("6226456952351452853");
return HttpResult.ok(testSensitive);
}
@Data
static class TestSensitive {
/**
* 不脱敏消息
*/
@Sensitive(strategy = SensitiveStrategy.DEFAULT)
private String message;
/**
* 自定义规则脱敏
*/
@Sensitive(strategy = SensitiveStrategy.CUSTOMIZE_RULE, startLen = 3, endLen = 6)
private String ruleMessage;
/**
* 身份证
*/
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
private String idCard;
/**
* 电话
*/
@Sensitive(strategy = SensitiveStrategy.PHONE, roleCode = "admin")
private String phone;
/**
* 地址
*/
@Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
private String address;
/**
* 邮箱
*/
@Sensitive(strategy = SensitiveStrategy.EMAIL, roleCode = "admin", perms = "system:user:query")
private String email;
/**
* 银行卡
*/
@Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleCode = "visitor", perms = "system:user:query")
private String bankCard;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66