最近做了个脱敏的需求,要对系统中的敏感信息,如手机号、车牌号、身份证号、银行卡号等进行脱敏显示。
效果类似下面这样:
简单来说,就是对敏感信息中的某几位进行掩码显示,常见的一般是使用*。
本篇文章就来讲解下在项目中该如何优雅的实现脱敏。
1. 工具类
首先,需要明确下脱敏规则:
-
手机号
显示前3位和后4位,中间4位脱敏,如
182****6791
。 -
车牌号
显示前3位和后1位,其余位脱敏,如
沪B0***8
。 -
身份证号
显示前3位和后4位,其余位脱敏,如
410***********0007
。 -
银行卡号
显示前4位和后4位,其余位脱敏,并且每4位分隔,如
6214 **** **** 8533
。
然后,需要一个实现了以上脱敏规则的工具类,这个工具类可以使用公司统一提供的,也可以使用第三方类库的,
或者使用自己实现的。
本篇文章使用第三方类库Hutool中自带的脱敏工具类DesensitizedUtil。
如果项目中之前没有使用过Hutool,需要添加以下依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.4</version>
</dependency>
使用示例:
System.out.println(DesensitizedUtil.mobilePhone("18216556791"));
System.out.println(DesensitizedUtil.carLicense("沪B08U28"));
System.out.println(DesensitizedUtil.idCardNum("410328200001010007", 3, 4));
System.out.println(DesensitizedUtil.bankCard("6214856213978533"));
输出结果:
2. 硬编码方案
所谓硬编码方案,就是在所有需要脱敏的地方都调用下上面工具类提供的方法。
这种方案的优点是简单,容易理解,能很好的评估改动影响的范围,缺点是代码耦合度高,如果涉及脱敏的接口很多,
那改动起来简直是灾难。
因此,这种方案推荐在涉及脱敏的接口很少的情况下使用,如果涉及脱敏的接口很多,推荐使用下文讲解的使用注解的方案。
3. 使用注解方案
使用注解方案,指的是接口数据在返回之前,通过执行自定义序列化器的逻辑达到脱敏的效果,
这种方案需要在字段上添加自定义注解来标识这个字段需要进行脱敏以及脱敏的规则,
优点是代码耦合度低,脱敏代码和业务代码不耦合,缺点是有一定的理解成本,不能很好的评估改动影响的范围。
那如何使用注解来实现脱敏呢?
首先,定义一个脱敏类型的枚举:
/**
* 脱敏类型
*/
public enum DesensitizeType {
/**
* 手机号
*/
MOBILE_PHONE,
/**
* 车牌号
*/
LICENSE_NUMBER,
/**
* 身份证号
*/
ID_CARD,
/**
* 银行卡
*/
BANK_CARD,
/**
* 自定义
*/
CUSTOM
}
然后,定义一个脱敏的注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeSerializer.class)
public @interface Desensitize {
/**
* 脱敏类型
*/
DesensitizeType type() default DesensitizeType.CUSTOM;
/**
* 开始位置(包含)
*/
int startIndex() default 0;
/**
* 结束位置(不包含)
*/
int endIndex() default 0;
}
上面代码中的@Target
和@Retention
是JDK自带的元注解,@JacksonAnnotationsInside和@JsonSerialize属于
jackson-databind下的注解,而spring-boot-starter-web下包含了jackson-databind,因此大部分项目不需要单独添加依赖:
重点看下@JsonSerialize(using = DesensitizeSerializer.class)
,该行代码指定json序列化时使用自定义的
脱敏序列化类DesensitizeSerializer,其代码如下所示:
/**
* 脱敏序列化类
*/
public class DesensitizeSerializer extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizeType type;
private Integer startIndex;
private Integer endIndex;
public DesensitizeSerializer() {
}
public DesensitizeSerializer(DesensitizeType type, Integer startIndex, Integer endIndex) {
this.type = type;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
@Override
public void serialize(String value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
switch (type) {
// 手机号脱敏
case MOBILE_PHONE:
jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(value)));
break;
// 车牌号脱敏
case LICENSE_NUMBER:
jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(value)));
break;
// 身份证号脱敏
case ID_CARD:
jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(value), 3, 4));
break;
// 银行卡脱敏
case BANK_CARD:
jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(value)))