jeecgboot 数据脱敏的功能流程及代码

250 阅读6分钟

jeecgboot 框架中文档中,提供了数据脱敏的功能,框架中作者是定义了三个注解来进行进行数据加密和解密。

image.png

上图介绍中, @SensitiveEncode 注解主要用于对返回实体类的带有 @SensitiveField 注解的字段进行一个加密;@SensitiveDecode 注解主要用于对返回实体类的带有 @SensitiveField 注解的字段进行一个解密。接下来进入源码,看看作者是怎么实现数据加密、解密功能的。

一、创建注解、切面、枚举、处理类

在jeecgboot文件中,实现该功能的包在 \jeecg-boot-master\jeecg-boot-base-core\src\main\java\org\jeecg\common\desensitization 目录下,annotation包自定义注解,aspect包存放切面方法,enums包存放的是枚举,util存的是工具类。

image.png

其中 annotation 包中定义了三个注解,分别是 @SensitiveField 、 @SensitiveEncode 和 @SensitiveDecode。

@SensitiveField 注解代码如下:

image.png

该注解默认值 SensitiveEnum.ENCODE(默认加密,解密的时候,字段也只能是该值)

@SensitiveEncode 注解代码如下:

image.png

@SensitiveDecode 注解代码如下:

image.png

@SensitiveEncode@SensitiveDecode 两个注解的参数是实体类,如果没设值,默认就是方法返回的实体类
如果方法返回的是集合、对象,在工具类出会进行处理,再加、解密。

二、创建切面

在 springboot 中,自定义注解可以通过 aop 的方法进行操作,jeecgboot 框架这几个注解也是这样实现的,进入到 \jeecg-boot-master\jeecg-boot-base-core\src\main\java\org\jeecg\common\desensitization\aspect\SensitiveDataAspect.java ,进入该类。

image.png

image.png

如上图,可以看到该类中的一系列操作。

1、拦截方法

通过 @Pointcut 和 @Around 拦截到 @SensitiveEncode、@SensitiveDecode 两个注解的方法,并对其进行切面操作。

2、特殊情况不做处理

image.png

如上图,在切面类代码中,有两种情况不做处理,分别是使用 @SensitiveEncode、@SensitiveDecode 注解的方法返回值为null或者返回基本数据类型。

3、定义变量

image.png

如上图,该切面定义了一系列变量:

isEncode(boolean类型):是否加密,默认加密(true)

entity(class):获取方法注解信息:是哪个实体

方法上使用@SensitiveEncode@SensitiveDecode注解时如果不设置值,默认是方法返回值,这时候通过method.getAnnotation(SensitiveEncode.class)就能拿到方法的返回值了。

methodSignature(MethodSignature):获取原始方法的信息,可以用于通配符匹配切入点时,区分不同的原始方法

method(Method):保存代理类的method对象

encode(SensitiveEncode):保存你需要获取到的注解

startTime、endTime(long):记录开始时间和结束时间

该处的流程就是:先获取切面拦截到的方法 -> 获取原方法返回结果 -> 再获取该方法是否有加密、解密的注解(确定isEncode值) -> 获取方法加密、解密信息(确定entity值)

4、分情况操作

image.png

如上图,这里作者分了三种情况,分别是(1)方法返回实体和注解的entity一样、(2)方法返回List<实体>、(3)方法返回一个对象

4.1、方法返回实体和注解的entity一样

进入到SensitiveInfoUtil.handlerObject(result, isEncode),代码如下:

image.png

通过代码可以看到,作者定义了fields数组获取结果类中的所有属性,通过for遍历每一个属性,如果属性带有 @SensitiveField 注解(isSensitiveField == true),则会继续判断是否为string字符串。

到这里可以发现,如果要对数据加密、或者解密,方法返回值不能是基本数据类型,加密字段必须有 @SensitiveField 注解,并且该字段属性类型需要为String类型。

满足上面条件后,定义realValue 保存带有 @SensitiveField 注解的属性值,空字符串和null不做加密处理;之后通过sf属性获取 @SensitiveField 注解;最后根据isEncode判断加密还是解密,加密调用SensitiveInfoUtil.getEncodeData(realValue, sf.type())方法,解密调用String value = SensitiveInfoUtil.getDecodeData(realValue)方法。

解密只对 SensitiveEnum.ENCODE 类型的字段解密。
4.1.1、加密代码

进入到SensitiveInfoUtil.getEncodeData()方法中:

image.png

如上图,该类中提供了多种加密方法,SensitiveEnum.ENCODE 类型的加密是采用aes对称加密。

image.png

image.png

如上图是作者写的一个加密算法,其中密钥和偏移量也是自己设置的两个值。

除此之外,还有其他加密,如电话加密、名字加密、邮箱加密等,这些只是屏蔽原数据的一部分内容

加密后给结果重新设值。

4.1.2、解密代码

进入到SensitiveInfoUtil.getDecodeData()中:

image.png

如上图,作者通过AesEncryptUtil.desEncrypt()对数据解密,并且返回。

image.png

如上图,作者也是写了一个解密算法进行解密。

这里作者也有对debug模式下,数据未加密而解密导致数据为空的情况做了处理,就是如果解密后数据是空的,就直接返回原数据,不解密了。

解密后给结果重新设值。

4.2、方法返回List<实体>

如果方法返回的是List<实体>,则调用的是SensitiveInfoUtil.handleList(result, entity, isEncode)方法,进入该方法:

image.png

如上图,该方法最终调用的还是handlerObject(temp, isEncode),也就是走4.1的操作步骤,只不过在走4.1步骤之前,多了几个前置操作,一起看看。

该方法参数是List集合、加密类型、是否加密。

首先通过list保存结果集,当结果集数据大于0时,获取第一条数据来判断是否跟加密的类一致。

感觉比较结果集大于0这一部分不是很有必要,在SensitiveDataAspect中已经判断需要非空后才能到这里,这里又进行一次判断非空,不过应该是为了更安全吧。

满足条件后,对List集合中的每一条数据都进行4.1步骤操作,最终达到全部加密。

4.2操作和4.1操作的区别:
(1)4.2操作需要判断集合中的类型是否为加密类的类型
(2)基于(1)后,4.2操作其实是对集合中的每一条数据进行4.1操作。
4.3、方法返回一个对象

如果返回的是一个对象,调用的是SensitiveInfoUtil.handleNestedObject(result, entity, isEncode)方法,进入该方法:

image.png

如上图,首先还是定义fields获取所有属性,然后分三种情况:

(1)基本类型不操作;

(2)里面是实体,获取实体,执行4.1操作;

(3)里面是集合,执行4.2操作。

5、返回结果

这里切面编程结束,返回结果。

总结:
1、jeecgboot框架对数据加密、或者解密,方法返回值不能是基本数据类型
2、加密字段必须有 @SensitiveField 注解,并且该字段属性类型需要为String类型
3、加密字段为null 或者 "" 时,不做处理