共计 12135 个字符,预计需要花费 31 分钟才能阅读完成。
我的项目介绍
日志脱敏是常见的平安需要。一般的基于工具类办法的形式,对代码的入侵性太强,编写起来又特地麻烦。
sensitive 提供了基于注解的形式,并且内置了常见的脱敏形式,便于开发。
日志脱敏
为了金融交易的安全性,国家强制规定对于以下信息是要日志脱敏的:
- 用户名
- 手机号
- 邮箱
- 银行卡号
- 明码
- 身份证号
长久化加密
存储的时候下面的信息都须要加密,明码为不可逆加密,其余为可逆加密。
相似的性能有很多。不在本零碎的解决范畴内。
个性
- 基于注解的日志脱敏。
- 能够自定义策略实现,策略失效条件。
- 内置常见的十几种脱敏内置计划。
- java 深拷贝,且原始对象不必实现任何接口。
- 反对用户自定义注解。
- 反对基于 FastJSON 间接生成脱敏后的 json
变更日志
变更日志
疾速开始
环境筹备
JDK 7+
Maven 3.x
maven 导入
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-core</artifactId>
<version>1.0.0</version>
</dependency>
外围 api 简介
SensitiveUtil
工具类的外围办法列表如下:
序号 | 办法 | 参数 | 后果 | 阐明 |
---|---|---|---|---|
1 | desCopy() | 指标对象 | 深度拷贝脱敏对象 | 适应性更强 |
2 | desJson() | 指标对象 | 脱敏对象 json | 性能较好 |
3 | desCopyCollection() | 指标对象汇合 | 深度拷贝脱敏对象汇合 | |
4 | desJsonCollection() | 指标对象汇合 | 脱敏对象 json 汇合 |
定义对象
- UserAnnotationBean.java
通过注解,指定每一个字段的脱敏策略。
public class UserAnnotationBean {
@SensitiveStrategyChineseName
private String username;
@SensitiveStrategyPassword
private String password;
@SensitiveStrategyPassport
private String passport;
@SensitiveStrategyIdNo
private String idNo;
@SensitiveStrategyCardId
private String bandCardId;
@SensitiveStrategyPhone
private String phone;
@SensitiveStrategyEmail
private String email;
@SensitiveStrategyAddress
private String address;
@SensitiveStrategyBirthday
private String birthday;
@SensitiveStrategyGps
private String gps;
@SensitiveStrategyIp
private String ip;
@SensitiveStrategyMaskAll
private String maskAll;
@SensitiveStrategyMaskHalf
private String maskHalf;
@SensitiveStrategyMaskRange
private String maskRange;
//Getter & Setter
//toString()}
- 数据筹备
构建一个最简略的测试对象:
UserAnnotationBean bean = new UserAnnotationBean();
bean.setUsername("张三");
bean.setPassword("123456");
bean.setPassport("CN1234567");
bean.setPhone("13066668888");
bean.setAddress("中国上海市浦东新区外滩 18 号");
bean.setEmail("whatanice@code.com");
bean.setBirthday("20220831");
bean.setGps("66.888888");
bean.setIp("127.0.0.1");
bean.setMaskAll("可恶啊我会被全副覆盖");
bean.setMaskHalf("还好我只会被覆盖一半");
bean.setMaskRange("我比拟灵便指定覆盖范畴");
bean.setBandCardId("666123456789066");
bean.setIdNo("360123202306018888");
- 测试代码
final String originalStr = "UserAnnotationBean{username=' 张三 ', password='123456', passport='CN1234567', idNo='360123202306018888', bandCardId='666123456789066', phone='13066668888', email='whatanice@code.com', address=' 中国上海市浦东新区外滩 18 号 ', birthday='20220831', gps='66.888888', ip='127.0.0.1', maskAll=' 可恶啊我会被全副覆盖 ', maskHalf=' 还好我只会被覆盖一半 ', maskRange=' 我比拟灵便指定覆盖范畴 '}";
final String sensitiveStr = "UserAnnotationBean{username=' 张 *', password='null', passport='CN*****67', idNo='3****************8', bandCardId='666123*******66', phone='1306****888', email='wh************.com', address=' 中国上海 ******** 8 号 ', birthday='20*****1', gps='66*****88', ip='127***0.1', maskAll='**********', maskHalf=' 还好我只会 *****', maskRange=' 我 ********* 围 '}";
final String expectSensitiveJson = "{\"address\":\" 中国上海 ******** 8 号 \",\"bandCardId\":\"666123*******66\",\"birthday\":\"20*****1\",\"email\":\"wh************.com\",\"gps\":\"66*****88\",\"idNo\":\"3****************8\",\"ip\":\"127***0.1\",\"maskAll\":\"**********\",\"maskHalf\":\" 还好我只会 *****\",\"maskRange\":\" 我 ********* 围 \",\"passport\":\"CN*****67\",\"phone\":\"1306****888\",\"username\":\" 张 *\"}";
UserAnnotationBean sensitiveUser = SensitiveUtil.desCopy(bean);
Assert.assertEquals(sensitiveStr, sensitiveUser.toString());
Assert.assertEquals(originalStr, bean.toString());
String sensitiveJson = SensitiveUtil.desJson(bean);
Assert.assertEquals(expectSensitiveJson, sensitiveJson);
咱们能够间接利用 sensitiveUser
去打印日志信息,而这个对象对于代码其余流程不影响,咱们仍然能够应用原来的 user
对象。
当然,也能够应用 sensitiveJson
打印日志信息。
@Sensitive 注解
阐明
@SensitiveStrategyChineseName
这种注解是为了便于用户应用,实质上等价于 @Sensitive(strategy = StrategyChineseName.class)
。
@Sensitive
注解能够指定对应的脱敏策略。
内置注解与映射
编号 | 注解 | 等价 @Sensitive | 备注 |
---|---|---|---|
1 | @SensitiveStrategyChineseName |
@Sensitive(strategy = StrategyChineseName.class) |
中文名称脱敏 |
2 | @SensitiveStrategyPassword |
@Sensitive(strategy = StrategyPassword.class) |
明码脱敏 |
3 | @SensitiveStrategyEmail |
@Sensitive(strategy = StrategyEmail.class) |
email 脱敏 |
4 | @SensitiveStrategyCardId |
@Sensitive(strategy = StrategyCardId.class) |
卡号脱敏 |
5 | @SensitiveStrategyPhone |
@Sensitive(strategy = StrategyPhone.class) |
手机号脱敏 |
6 | @SensitiveStrategyIdNo |
@Sensitive(strategy = StrategyIdNo.class) |
身份证脱敏 |
6 | @SensitiveStrategyAddress |
@Sensitive(strategy = StrategyAddress.class) |
地址脱敏 |
7 | @SensitiveStrategyGps |
@Sensitive(strategy = StrategyGps.class) |
GPS 脱敏 |
8 | @SensitiveStrategyIp |
@Sensitive(strategy = StrategyIp.class) |
IP 脱敏 |
9 | @SensitiveStrategyBirthday |
@Sensitive(strategy = StrategyBirthday.class) |
生日脱敏 |
10 | @SensitiveStrategyPassport |
@Sensitive(strategy = StrategyPassport.class) |
护照脱敏 |
11 | @SensitiveStrategyMaskAll |
@Sensitive(strategy = StrategyMaskAll.class) |
全副脱敏 |
12 | @SensitiveStrategyMaskHalf |
@Sensitive(strategy = StrategyMaskHalf.class) |
一半脱敏 |
13 | @SensitiveStrategyMaskRange |
@Sensitive(strategy = StrategyMaskRange.class) |
指定范畴脱敏 |
@Sensitive 定义
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
/**
* 注解失效的条件
* @return 条件对应的实现类
*/
Class<? extends ICondition> condition() default ConditionAlwaysTrue.class;
/**
* 执行的策略
* @return 策略对应的类型
*/
Class<? extends IStrategy> strategy();}
与 @Sensitive 混合应用
如果你将新增的注解 @SensitiveStrategyChineseName
与 @Sensitive
同时在一个字段上应用。
为了简化逻辑,优先选择执行 @Sensitive
,如果 @Sensitive
执行脱敏,
那么 @SensitiveStrategyChineseName
将不会失效。
如:
/**
* 测试字段
* 1. 当多种注解混合的时候,为了简化逻辑,优先选择 @Sensitive 注解。*/
@SensitiveStrategyChineseName
@Sensitive(strategy = StrategyPassword.class)
private String testField;
更多个性
自定义脱敏策略失效的场景
默认状况下,咱们指定的场景都是失效的。
然而你可能须要有些状况下不进行脱敏,比方有些用户明码为 123456,你感觉这种用户不脱敏也罢。
- UserPasswordCondition.java
@Sensitive(condition = ConditionFooPassword.class, strategy = StrategyPassword.class)
private String password;
其余放弃不变,咱们指定了一个 condition,实现如下:
- ConditionFooPassword.java
public class ConditionFooPassword implements ICondition {
@Override
public boolean valid(IContext context) {
try {Field field = context.getCurrentField();
final Object currentObj = context.getCurrentObject();
final String password = (String) field.get(currentObj);
return !password.equals("123456");
} catch (IllegalAccessException e) {throw new RuntimeException(e);
}
}
}
也就是只有当明码不是 123456 时明码脱敏策略才会失效。
属性为汇合或者对象
如果某个属性是单个汇合或者对象,则须要应用注解 @SensitiveEntry
。
- 放在汇合属性上,且属性为一般对象
会遍历每一个属性,执行下面的脱敏策略。
- 放在对象属性上
会解决对象中各个字段上的脱敏注解信息。
- 放在汇合属性上,且属性为对象
遍历每一个对象,解决对象中各个字段上的脱敏注解信息。
放在汇合属性上,且属性为一般对象
- UserEntryBaseType.java
作为演示,汇合中为一般的字符串。
public class UserEntryBaseType {
@SensitiveEntry
@Sensitive(strategy = StrategyChineseName.class)
private List<String> chineseNameList;
@SensitiveEntry
@Sensitive(strategy = StrategyChineseName.class)
private String[] chineseNameArray;
//Getter & Setter & toString()}
放在对象属性上
例子如下:
public class UserEntryObject {
@SensitiveEntry
private User user;
@SensitiveEntry
private List<User> userList;
@SensitiveEntry
private User[] userArray;
//...
}
自定义注解
- v0.0.4 新增性能。容许性能自定义条件注解和策略注解。
- v0.0.11 新增性能。容许性能自定义级联脱敏注解。
案例 1
自定义明码脱敏策略 & 自定义明码脱敏策略失效条件
- 策略脱敏
/**
* 自定义明码脱敏策略
* @author binbin.hou
* date 2019/1/17
* @since 0.0.4
*/
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveStrategy(CustomPasswordStrategy.class)
public @interface SensitiveCustomPasswordStrategy {}
- 脱敏失效条件
/**
* 自定义明码脱敏策略失效条件
* @author binbin.hou
* date 2019/1/17
* @since 0.0.4
*/
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveCondition(ConditionFooPassword.class)
public @interface SensitiveCustomPasswordCondition{}
- TIPS
@SensitiveStrategy
策略独自应用的时候,默认是失效的。
如果有 @SensitiveCondition
注解,则只有当条件满足时,才会执行脱敏策略。
@SensitiveCondition
只会对系统内置注解和自定义注解失效,因为 @Sensitive
有属于本人的策略失效条件。
- 策略优先级
@Sensitive
优先失效,而后是零碎内置注解,最初是用户自定义注解。
对应的实现
两个元注解 @SensitiveStrategy
、@SensitiveCondition
别离指定了对应的实现。
- CustomPasswordStrategy.java
public class CustomPasswordStrategy implements IStrategy {
@Override
public Object des(Object original, IContext context) {return "**********************";}
}
- ConditionFooPassword.java
/**
* 让这些 123456 的明码不进行脱敏
* @author binbin.hou
* date 2019/1/2
* @since 0.0.1
*/
public class ConditionFooPassword implements ICondition {
@Override
public boolean valid(IContext context) {
try {Field field = context.getCurrentField();
final Object currentObj = context.getCurrentObject();
final String name = (String) field.get(currentObj);
return !name.equals("123456");
} catch (IllegalAccessException e) {throw new RuntimeException(e);
}
}
}
定义测试对象
定义一个应用自定义注解的对象。
public class CustomPasswordModel {
@SensitiveCustomPasswordCondition
@SensitiveCustomPasswordStrategy
private String password;
@SensitiveCustomPasswordCondition
@SensitiveStrategyPassword
private String fooPassword;
// 其余办法
}
测试
/**
* 自定义注解测试
*/
@Test
public void customAnnotationTest() {final String originalStr = "CustomPasswordModel{password='hello', fooPassword='123456'}";
final String sensitiveStr = "CustomPasswordModel{password='**********************', fooPassword='123456'}";
CustomPasswordModel model = buildCustomPasswordModel();
Assert.assertEquals(originalStr, model.toString());
CustomPasswordModel sensitive = SensitiveUtil.desCopy(model);
Assert.assertEquals(sensitiveStr, sensitive.toString());
Assert.assertEquals(originalStr, model.toString());
}
构建对象的办法如下:
/**
* 构建自定义明码对象
* @return 对象
*/
private CustomPasswordModel buildCustomPasswordModel(){CustomPasswordModel model = new CustomPasswordModel();
model.setPassword("hello");
model.setFooPassword("123456");
return model;
}
案例 2
- v0.0.11 新增性能。容许性能自定义级联脱敏注解。
自定义级联脱敏注解
- 自定义级联脱敏注解
能够依据本人的业务须要,在自定义的注解上应用 @SensitiveEntry
。
应用形式放弃和 @SensitiveEntry
一样即可。
/**
* 级联脱敏注解, 如果对象中属性为另外一个对象 (汇合),则能够应用这个注解指定。* <p>
* 1. 如果属性为 Iterable 的子类汇合,则当做列表解决,遍历其中的对象
* 2. 如果是一般对象,则解决对象中的脱敏信息
* 3. 如果是一般字段 /MAP,则不做解决
* @since 0.0.11
*/
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveEntry
public @interface SensitiveEntryCustom {}
定义测试对象
定义一个应用自定义注解的对象。
public class CustomUserEntryObject {
@SensitiveEntryCustom
private User user;
@SensitiveEntryCustom
private List<User> userList;
@SensitiveEntryCustom
private User[] userArray;
// 其余办法...
}
生成脱敏后的 JSON
阐明
为了防止生成两头脱敏对象,v0.0.6 之后间接反对生成脱敏后的 JSON。
应用办法
新增工具类办法,能够间接返回脱敏后的 JSON。
生成的 JSON 是脱敏的,原对象属性值不受影响。
public static String desJson(Object object)
注解的应用形式
和 SensitiveUtil.desCopy()
完全一致。
应用示例代码
所有的测试案例中,都增加了对应的 desJson(Object)
测试代码,能够参考。
此处只展现最根本的应用。
final String originalStr = "SystemBuiltInAt{phone='18888888888', password='1234567', name=' 脱敏君 ', email='12345@qq.com', cardId='123456190001011234'}";
final String sensitiveJson = "{\"cardId\":\"123456**********34\",\"email\":\"12******.com\",\"name\":\" 脱 **\",\"phone\":\"1888****888\"}";
SystemBuiltInAt systemBuiltInAt = DataPrepareTest.buildSystemBuiltInAt();
Assert.assertEquals(sensitiveJson, SensitiveUtil.desJson(systemBuiltInAt));
Assert.assertEquals(originalStr, systemBuiltInAt.toString());
留神
本次 JSON 脱敏基于 FastJSON。
FastJSON 在序列化自身存在肯定限度。当对象中有汇合,汇合中还是对象时,后果不尽如人意。
示例代码
本测试案例可见测试代码。
final String originalStr = "UserCollection{userList=[User{username=' 脱敏君 ', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}], userSet=[User{username=' 脱敏君 ', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}], userCollection=[User{username=' 脱敏君 ', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}], userMap={map=User{username=' 脱敏君 ', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}}}";
final String commonJson = "{\"userArray\":[{\"email\":\"12345@qq.com\",\"idCard\":\"123456190001011234\",\"password\":\"1234567\",\"phone\":\"18888888888\",\"username\":\" 脱敏君 \"}],\"userCollection\":[{\"$ref\":\"$.userArray[0]\"}],\"userList\":[{\"$ref\":\"$.userArray[0]\"}],\"userMap\":{\"map\":{\"$ref\":\"$.userArray[0]\"}},\"userSet\":[{\"$ref\":\"$.userArray[0]\"}]}";
final String sensitiveJson = "{\"userArray\":[{\"email\":\"12******.com\",\"idCard\":\"123456**********34\",\"phone\":\"1888****888\",\"username\":\" 脱 **\"}],\"userCollection\":[{\"$ref\":\"$.userArray[0]\"}],\"userList\":[{\"$ref\":\"$.userArray[0]\"}],\"userMap\":{\"map\":{\"$ref\":\"$.userArray[0]\"}},\"userSet\":[{\"$ref\":\"$.userArray[0]\"}]}";
UserCollection userCollection = DataPrepareTest.buildUserCollection();
Assert.assertEquals(commonJson, JSON.toJSONString(userCollection));
Assert.assertEquals(sensitiveJson, SensitiveUtil.desJson(userCollection));
Assert.assertEquals(originalStr, userCollection.toString());
解决方案
如果有这种需要,倡议应用原来的 desCopy(Object)
。
脱敏疏导类
为了配置的灵活性,引入了疏导类。
外围 api 简介
SensitiveBs
疏导类的外围办法列表如下:
序号 | 办法 | 参数 | 后果 | 阐明 |
---|---|---|---|---|
1 | desCopy() | 指标对象 | 深度拷贝脱敏对象 | 适应性更强 |
2 | desJson() | 指标对象 | 脱敏对象 json | 性能较好 |
应用示例
应用形式和工具类统一,示意如下:
SensitiveBs.newInstance().desCopy(user);
配置深度拷贝实现
默认的应用 FastJson 进行对象的深度拷贝,等价于:
SensitiveBs.newInstance()
.deepCopy(FastJsonDeepCopy.getInstance())
.desJson(user);
参见 SensitiveBsTest.java
deepCopy 用于指定深度复制的具体实现,反对用户自定义。
深度复制(DeepCopy)
阐明
深度复制能够保障咱们日志输入对象脱敏,同时不影响失常业务代码的应用。
能够实现深度复制的形式有很多种,默认基于 fastjson 实现的。
为保障后续良性倒退,v0.0.13 版本之后将深度复制接口抽离为独自的我的项目:
deep-copy
内置策略
目前反对 6 种基于序列化实现的深度复制,便于用户替换应用。
每一种都能够独自应用,保障依赖更加轻量。
自定义
为满足不同场景的需要,深度复制策略反对用户自定义。
自定义深度复制
开源地址
https://github.com/houbb/sensitive