新聞中心
項(xiàng)目介紹
日志脫敏是常見的安全需求。普通的基于工具類方法的方式,對(duì)代碼的入侵性太強(qiáng),編寫起來又特別麻煩。

10年積累的網(wǎng)站設(shè)計(jì)、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對(duì)客戶對(duì)網(wǎng)站的新想法和需求。提供各種問題對(duì)應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識(shí)你,你也不認(rèn)識(shí)我。但先建設(shè)網(wǎng)站后付款的網(wǎng)站建設(shè)流程,更有呼中免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
sensitive[1] 提供了基于注解的方式,并且內(nèi)置了常見的脫敏方式,便于開發(fā)。
日志脫敏
為了金融交易的安全性,國家強(qiáng)制規(guī)定對(duì)于以下信息是要日志脫敏的:
- 用戶名
- 手機(jī)號(hào)
- 郵箱
- 銀行卡號(hào)
- 密碼
- 身份證號(hào)
特性
- 基于注解的日志脫敏。
- 可以自定義策略實(shí)現(xiàn),策略生效條件。
- 內(nèi)置常見的十幾種脫敏內(nèi)置方案。
- java 深拷貝,且原始對(duì)象不用實(shí)現(xiàn)任何接口。
- 支持用戶自定義注解。
- 支持基于 FastJSON 直接生成脫敏后的 json。
快速開始
環(huán)境準(zhǔn)備
- JDK 7+
- Maven 3.x
maven 導(dǎo)入
com.github.houbb
sensitive-core
1.0.0
核心 api 簡(jiǎn)介
SensitiveUtil 工具類的核心方法列表如下:
|
序號(hào) |
方法 |
參數(shù) |
結(jié)果 |
說明 |
|
1 |
desCopy() |
目標(biāo)對(duì)象 |
深度拷貝脫敏對(duì)象 |
適應(yīng)性更強(qiáng) |
|
2 |
desJson() |
目標(biāo)對(duì)象 |
脫敏對(duì)象 json |
性能較好 |
|
3 |
desCopyCollection() |
目標(biāo)對(duì)象集合 |
深度拷貝脫敏對(duì)象集合 | |
|
4 |
desJsonCollection() |
目標(biāo)對(duì)象集合 |
脫敏對(duì)象 json 集合 |
定義對(duì)象
- UserAnnotationBean.java
通過注解,指定每一個(gè)字段的脫敏策略。
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()
}
- 數(shù)據(jù)準(zhǔn)備
構(gòu)建一個(gè)最簡(jiǎn)單的測(cè)試對(duì)象:
UserAnnotationBean bean = new UserAnnotationBean();
bean.setUsername("張三");
bean.setPassword("123456");
bean.setPassport("CN1234567");
bean.setPhone("13066668888");
bean.setAddress("中國上海市浦東新區(qū)外灘18號(hào)");
bean.setEmail("[email protected]");
bean.setBirthday("20220831");
bean.setGps("66.888888");
bean.setIp("127.0.0.1");
bean.setMaskAll("可惡啊我會(huì)被全部掩蓋");
bean.setMaskHalf("還好我只會(huì)被掩蓋一半");
bean.setMaskRange("我比較靈活指定掩蓋范圍");
bean.setBandCardId("666123456789066");
bean.setIdNo("360123202306018888");
- 測(cè)試代碼
final String originalStr = "UserAnnotationBean{username='張三', password='123456', passport='CN1234567', idNo='360123202306018888', bandCardId='666123456789066', phone='13066668888', email='[email protected]', address='中國上海市浦東新區(qū)外灘18號(hào)', birthday='20220831', gps='66.888888', ip='127.0.0.1', maskAll='可惡啊我會(huì)被全部掩蓋', maskHalf='還好我只會(huì)被掩蓋一半', 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號(hào)', birthday='20*****1', gps='66*****88', ip='127***0.1', maskAll='**********', maskHalf='還好我只會(huì)*****', maskRange='我*********圍'}";
final String expectSensitiveJson = "{\"address\":\"中國上海********8號(hào)\",\"bandCardId\":\"666123*******66\",\"birthday\":\"20*****1\",\"email\":\"wh************.com\",\"gps\":\"66*****88\",\"idNo\":\"3****************8\",\"ip\":\"127***0.1\",\"maskAll\":\"**********\",\"maskHalf\":\"還好我只會(huì)*****\",\"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 去打印日志信息,而這個(gè)對(duì)象對(duì)于代碼其他流程不影響,我們依然可以使用原來的 user 對(duì)象。
當(dāng)然,也可以使用 sensitiveJson 打印日志信息。
@Sensitive 注解
說明
@SensitiveStrategyChineseName 這種注解是為了便于用戶使用,本質(zhì)上等價(jià)于 @Sensitive(strategy = StrategyChineseName.class)。
@Sensitive 注解可以指定對(duì)應(yīng)的脫敏策略。
內(nèi)置注解與映射
|
編號(hào) |
注解 |
等價(jià) @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) |
卡號(hào)脫敏 |
|
5 |
@SensitiveStrategyPhone |
@Sensitive(strategy = StrategyPhone.class) |
手機(jī)號(hào)脫敏 |
|
6 |
@SensitiveStrategyIdNo |
@Sensitive(strategy = StrategyIdNo.class) |
身份證脫敏 |
|
7 |
@SensitiveStrategyAddress |
@Sensitive(strategy = StrategyAddress.class) |
地址脫敏 |
|
8 |
@SensitiveStrategyGps |
@Sensitive(strategy = StrategyGps.class) |
GPS 脫敏 |
|
9 |
@SensitiveStrategyIp |
@Sensitive(strategy = StrategyIp.class) |
IP 脫敏 |
|
10 |
@SensitiveStrategyBirthday |
@Sensitive(strategy = StrategyBirthday.class) |
生日脫敏 |
|
11 |
@SensitiveStrategyPassport |
@Sensitive(strategy = StrategyPassport.class) |
護(hù)照脫敏 |
|
12 |
@SensitiveStrategyMaskAll |
@Sensitive(strategy = StrategyMaskAll.class) |
全部脫敏 |
|
13 |
@SensitiveStrategyMaskHalf |
@Sensitive(strategy = StrategyMaskHalf.class) |
一半脫敏 |
|
14 |
@SensitiveStrategyMaskRange |
@Sensitive(strategy = StrategyMaskRange.class) |
指定范圍脫敏 |
@Sensitive 定義
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
/**
* 注解生效的條件
* @return 條件對(duì)應(yīng)的實(shí)現(xiàn)類
*/
Class extends ICondition> condition() default ConditionAlwaysTrue.class;
/**
* 執(zhí)行的策略
* @return 策略對(duì)應(yīng)的類型
*/
Class extends IStrategy> strategy();
}
與 @Sensitive 混合使用
如果你將新增的注解 @SensitiveStrategyChineseName 與 @Sensitive 同時(shí)在一個(gè)字段上使用。
為了簡(jiǎn)化邏輯,優(yōu)先選擇執(zhí)行 @Sensitive,如果 @Sensitive 執(zhí)行脫敏, 那么 @SensitiveStrategyChineseName 將不會(huì)生效。
如:
/**
* 測(cè)試字段
* 1.當(dāng)多種注解混合的時(shí)候,為了簡(jiǎn)化邏輯,優(yōu)先選擇 @Sensitive 注解。
*/
@SensitiveStrategyChineseName
@Sensitive(strategy = StrategyPassword.class)
private String testField;
更多特性
自定義脫敏策略生效的場(chǎng)景
默認(rèn)情況下,我們指定的場(chǎng)景都是生效的。
但是你可能需要有些情況下不進(jìn)行脫敏,比如有些用戶密碼為 123456,你覺得這種用戶不脫敏也罷。
- UserPasswordCondition.java
@Sensitive(condition = ConditionFooPassword.class, strategy = StrategyPassword.class)
private String password;其他保持不變,我們指定了一個(gè) condition,實(shí)現(xiàn)如下:
- 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);
}
}
}也就是只有當(dāng)密碼不是 123456 時(shí)密碼脫敏策略才會(huì)生效。
屬性為集合或者對(duì)象
如果某個(gè)屬性是單個(gè)集合或者對(duì)象,則需要使用注解 @SensitiveEntry。
- 放在集合屬性上,且屬性為普通對(duì)象
會(huì)遍歷每一個(gè)屬性,執(zhí)行上面的脫敏策略。
- 放在對(duì)象屬性上
會(huì)處理對(duì)象中各個(gè)字段上的脫敏注解信息。
- 放在集合屬性上,且屬性為對(duì)象
遍歷每一個(gè)對(duì)象,處理對(duì)象中各個(gè)字段上的脫敏注解信息。
放在集合屬性上,且屬性為普通對(duì)象
- UserEntryBaseType.java
作為演示,集合中為普通的字符串。
public class UserEntryBaseType {
@SensitiveEntry
@Sensitive(strategy = StrategyChineseName.class)
private List chineseNameList;
@SensitiveEntry
@Sensitive(strategy = StrategyChineseName.class)
private String[] chineseNameArray;
//Getter & Setter & toString()
}
放在對(duì)象屬性上
例子如下:
public class UserEntryObject {
@SensitiveEntry
private User user;
@SensitiveEntry
private List userList;
@SensitiveEntry
private User[] userArray;
//...
}
自定義注解
- v0.0.4 新增功能。允許功能自定義條件注解和策略注解。?v0.0.11 新增功能。允許功能自定義級(jí)聯(lián)脫敏注解。
案例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 策略單獨(dú)使用的時(shí)候,默認(rèn)是生效的。
如果有 @SensitiveCondition 注解,則只有當(dāng)條件滿足時(shí),才會(huì)執(zhí)行脫敏策略。
@SensitiveCondition 只會(huì)對(duì)系統(tǒng)內(nèi)置注解和自定義注解生效,因?yàn)?nbsp;@Sensitive 有屬于自己的策略生效條件。
- 策略優(yōu)先級(jí)
@Sensitive 優(yōu)先生效,然后是系統(tǒng)內(nèi)置注解,最后是用戶自定義注解。
對(duì)應(yīng)的實(shí)現(xiàn)
兩個(gè)元注解 @SensitiveStrategy、@SensitiveCondition 分別指定了對(duì)應(yīng)的實(shí)現(xiàn)。
- CustomPasswordStrategy.java
public class CustomPasswordStrategy implements IStrategy {
@Override
public Object des(Object original, IContext context) {
return "**********************";
}
}
- ConditionFooPassword.java
/**
* 讓這些 123456 的密碼不進(jìn)行脫敏
* @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);
}
}
}
定義測(cè)試對(duì)象
定義一個(gè)使用自定義注解的對(duì)象。
public class CustomPasswordModel {
@SensitiveCustomPasswordCondition
@SensitiveCustomPasswordStrategy
private String password;
@SensitiveCustomPasswordCondition
@SensitiveStrategyPassword
private String fooPassword;
//其他方法
}
測(cè)試
/**
* 自定義注解測(cè)試
*/
@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());
}構(gòu)建對(duì)象的方法如下:
/**
* 構(gòu)建自定義密碼對(duì)象
* @return 對(duì)象
*/
private CustomPasswordModel buildCustomPasswordModel(){
CustomPasswordModel model = new CustomPasswordModel();
model.setPassword("hello");
model.setFooPassword("123456");
return model;
}
案例2
- v0.0.11 新增功能。允許功能自定義級(jí)聯(lián)脫敏注解。
自定義級(jí)聯(lián)脫敏注解
- 自定義級(jí)聯(lián)脫敏注解
可以根據(jù)自己的業(yè)務(wù)需要,在自定義的注解上使用 @SensitiveEntry。
使用方式保持和 @SensitiveEntry 一樣即可。
/**
* 級(jí)聯(lián)脫敏注解,如果對(duì)象中屬性為另外一個(gè)對(duì)象(集合),則可以使用這個(gè)注解指定。
*
* 1. 如果屬性為 Iterable 的子類集合,則當(dāng)做列表處理,遍歷其中的對(duì)象
* 2. 如果是普通對(duì)象,則處理對(duì)象中的脫敏信息
* 3. 如果是普通字段/MAP,則不做處理
* @since 0.0.11
*/
@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveEntry
public @interface SensitiveEntryCustom {
}
定義測(cè)試對(duì)象
定義一個(gè)使用自定義注解的對(duì)象。
public class CustomUserEntryObject {
@SensitiveEntryCustom
private User user;
@SensitiveEntryCustom
private List userList;
@SensitiveEntryCustom
private User[] userArray;
// 其他方法...
}
生成脫敏后的 JSON
說明
為了避免生成中間脫敏對(duì)象,v0.0.6 之后直接支持生成脫敏后的 JSON。
使用方法
新增工具類方法,可以直接返回脫敏后的 JSON。
生成的 JSON 是脫敏的,原對(duì)象屬性值不受影響。
public static String desJson(Object object)
注解的使用方式
和 SensitiveUtil.desCopy() 完全一致。
使用示例代碼
所有的測(cè)試案例中,都添加了對(duì)應(yīng)的 desJson(Object) 測(cè)試代碼,可以參考。
此處只展示最基本的使用。
final String originalStr = "SystemBuiltInAt{phone='18888888888', password='1234567', name='脫敏君', email='[email protected]', 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[2]。
FastJSON 在序列化本身存在一定限制。當(dāng)對(duì)象中有集合,集合中還是對(duì)象時(shí),結(jié)果不盡如人意。
示例代碼
本測(cè)試案例可見測(cè)試代碼。
final String originalStr = "UserCollection{userList=[User{username='脫敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}], userSet=[User{username='脫敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}], userCollection=[User{username='脫敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}], userMap={map=User{username='脫敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}}}";
final String commonJson = "{\"userArray\":[{\"email\":\"[email protected]\",\"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)。
脫敏引導(dǎo)類
為了配置的靈活性,引入了引導(dǎo)類。
核心 api 簡(jiǎn)介
SensitiveBs 引導(dǎo)類的核心方法列表如下:
|
序號(hào) |
方法 |
參數(shù) |
結(jié)果 |
說明 |
|
1 |
desCopy() |
目標(biāo)對(duì)象 |
深度拷貝脫敏對(duì)象 |
適應(yīng)性更強(qiáng) |
|
2 |
desJson() |
目標(biāo)對(duì)象 |
脫敏對(duì)象 json |
性能較好 |
使用示例
使用方式和工具類一致,示意如下:
SensitiveBs.newInstance().desCopy(user);
配置深度拷貝實(shí)現(xiàn)
默認(rèn)的使用 FastJson 進(jìn)行對(duì)象的深度拷貝,等價(jià)于:
SensitiveBs.newInstance()
.deepCopy(FastJsonDeepCopy.getInstance())
.desJson(user);參見 SensitiveBsTest.java[3]。
deepCopy 用于指定深度復(fù)制的具體實(shí)現(xiàn),支持用戶自定義。
深度復(fù)制(DeepCopy)
說明
深度復(fù)制可以保證我們?nèi)罩据敵鰧?duì)象脫敏,同時(shí)不影響正常業(yè)務(wù)代碼的使用。
可以實(shí)現(xiàn)深度復(fù)制的方式有很多種,默認(rèn)基于 fastjson[4] 實(shí)現(xiàn)的。
為保證后續(xù)良性發(fā)展,v0.0.13 版本之后將深度復(fù)制接口抽離為單獨(dú)的項(xiàng)目:
deep-copy[5]
內(nèi)置策略
目前支持 6 種基于序列化實(shí)現(xiàn)的深度復(fù)制,便于用戶替換使用。
每一種都可以單獨(dú)使用,保證依賴更加輕量。
自定義
為滿足不同場(chǎng)景的需求,深度復(fù)制策略支持用戶自定義。
自定義深度復(fù)制[6]
開源地址
https://github.com/houbb/sensitive [7]。
References
[1] sensitive: https://github.com/houbb/sensitive。
[2] FastJSON: https://github.com/alibaba/fastjson。
[3] SensitiveBsTest.java: https://github.com/houbb/sensitive/blob/master/sensitive-test/src/test/java/com/github/houbb/sensitive/test/bs/SensitiveBsTest.java。
[4] fastjson: https://github.com/alibaba/fastjson。
[5] deep-copy: https://github.com/houbb/deep-copy。
[6] 自定義深度復(fù)制: https://github.com/houbb/deep-copy#自定義。
[7] https://github.com/houbb/sensitive : https://github.com/houbb/sensitive。
網(wǎng)頁名稱:金融用戶敏感數(shù)據(jù)如何優(yōu)雅地實(shí)現(xiàn)脫敏?
分享路徑:http://m.fisionsoft.com.cn/article/cojhesh.html


咨詢
建站咨詢
