All Projects → houbb → Sensitive

houbb / Sensitive

Licence: apache-2.0
🔐Sensitive log tool for java, based on java annotation. (基于注解的 java 日志脱敏框架,更加优雅的日志打印)

Programming Languages

java
68154 projects - #9 most used programming language

Projects that are alternatives of or similar to Sensitive

Naza
🍀 Go basic library. || Go语言基础库
Stars: ✭ 253 (+26.5%)
Mutual labels:  json, log
Log4rs
A highly configurable logging framework for Rust
Stars: ✭ 483 (+141.5%)
Mutual labels:  json, log
Datoji
A tiny JSON storage service. Create, Read, Update, Delete and Search JSON data.
Stars: ✭ 222 (+11%)
Mutual labels:  json, fastjson
Fastjson
A fast JSON parser/generator for Java.
Stars: ✭ 23,997 (+11898.5%)
Mutual labels:  json, fastjson
Xlog
Android logger, pretty, powerful and flexible, log to everywhere, save to file, all you want is here.
Stars: ✭ 2,468 (+1134%)
Mutual labels:  json, log
Co
Art of C++. Flag, logging, unit-test, json, go-style coroutine and more.
Stars: ✭ 2,264 (+1032%)
Mutual labels:  json, log
Fastjson
Smallest, fastest polymorphic JSON serializer
Stars: ✭ 446 (+123%)
Mutual labels:  json, fastjson
Easyjson
Provides an unified JSON access API, you can adapter any JSON library to Gson, Jackson, FastJson with easyjson。 提供了一个JSON门面库,就像slf4j一样。easyjson本身不做json的操作,完全依赖于底层实现库。可以直接使用Easyjson的API,底层的JSON库随时可切换。也可以使用其中某个json的API,然后通过easyjson适配给其他的json库
Stars: ✭ 54 (-73%)
Mutual labels:  json, fastjson
Logger json
JSON console backend for Elixir Logger.
Stars: ✭ 108 (-46%)
Mutual labels:  json, log
Fblog
Small command-line JSON Log viewer
Stars: ✭ 137 (-31.5%)
Mutual labels:  json, log
Tlog
Terminal I/O logger
Stars: ✭ 170 (-15%)
Mutual labels:  json, log
Hana
An implementation of JSON Patch and JSON Pointer
Stars: ✭ 196 (-2%)
Mutual labels:  json
Mxisd
Federated Matrix Identity Server
Stars: ✭ 194 (-3%)
Mutual labels:  json
Jl
jl — JSON Logs, a development tool for working with structured JSON logging.
Stars: ✭ 194 (-3%)
Mutual labels:  json
Tq
Perform a lookup by CSS selector on an HTML input
Stars: ✭ 193 (-3.5%)
Mutual labels:  json
Discovery
Frontend framework for rapid data (JSON) analysis, sharable serverless reports and dashboards
Stars: ✭ 199 (-0.5%)
Mutual labels:  json
Openermanifest
Set of rules powering Opener for iOS
Stars: ✭ 195 (-2.5%)
Mutual labels:  json
Bridge.
Minecraft Add-on Editor | We strive to provide the best development experience possible
Stars: ✭ 193 (-3.5%)
Mutual labels:  json
Csmodel
CSModel is a concise and efficient model framework for iOS/OSX, and provides nested Model to compare values and copy values.
Stars: ✭ 192 (-4%)
Mutual labels:  json
Masterchief
C# 开发辅助类库,和士官长一样身经百战且越战越勇的战争机器,能力无人能出其右。
Stars: ✭ 190 (-5%)
Mutual labels:  log

项目介绍

日志脱敏是常见的安全需求。普通的基于工具类方法的方式,对代码的入侵性太强。编写起来又特别麻烦。

本项目提供基于注解的方式,并且内置了常见的脱敏方式,便于开发。

用户也可以基于自己的实际需要,自定义注解。

Build Status Maven Central Open Source Love

变更日志

日志脱敏

为了金融交易的安全性,国家强制规定对于以下信息是要日志脱敏的:

  1. 用户名

  2. 手机号

  3. 邮箱

  4. 银行卡号

  5. 密码

持久化加密

存储的时候上面的信息都需要加密,密码为不可逆加密,其他为可逆加密。

类似的功能有很多。不在本系统的解决范围内。

特性

  1. 基于注解的日志脱敏。

  2. 可以自定义策略实现,策略生效条件。

  3. 常见的脱敏内置方案。

  4. java 深拷贝,且原始对象不用实现任何接口。

  5. 支持用户自定义注解。

  6. 支持基于 FastJSON 直接生成脱敏后的 json

v0.0.13 变更

  1. 将深度复制相关接口独立为单独的应用,便于后期拓展。

快速开始

环境准备

JDK 7+

Maven 3.x

maven 导入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>sensitive-core</artifactId>
    <version>0.0.13</version>
</dependency>

核心 api 简介

SensitiveUtil 工具类的核心方法列表如下:

序号 方法 参数 结果 说明
1 desCopy() 目标对象 深度拷贝脱敏对象 适应性更强
2 desJson() 目标对象 脱敏对象 json 性能较好
3 desCopyCollection() 目标对象集合 深度拷贝脱敏对象集合
4 desJsonCollection() 目标对象集合 脱敏对象 json 集合

定义对象

  • User.java

我们对 password 使用脱敏,指定脱敏策略为 StrategyPassword。(直接返回 null)

public class User {

    @Sensitive(strategy = StrategyChineseName.class)
    private String username;
    
    @Sensitive(strategy = StrategyCardId.class)
    private String idCard;
    
    @Sensitive(strategy = StrategyPassword.class)
    private String password;
    
    @Sensitive(strategy = StrategyEmail.class)
    private String email;
    
    @Sensitive(strategy = StrategyPhone.class)
    private String phone;
    
    //Getter & Setter
    //toString()
}
  • 测试
    @Test
    public void UserSensitiveTest() {
        User user = buildUser();
        System.out.println("脱敏前原始: " + user);
        User sensitiveUser = SensitiveUtil.desCopy(user);
        System.out.println("脱敏对象: " + sensitiveUser);
        System.out.println("脱敏后原始: " + user);
    }

    private User buildUser() {
        User user = new User();
        user.setUsername("脱敏君");
        user.setPassword("123456");
        user.setEmail("[email protected]");
        user.setIdCard("123456190001011234");
        user.setPhone("18888888888");
        return user;
    }
  • 输出信息如下
脱敏前原始: User{username='脱敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}
脱敏对象: User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}
脱敏后原始: User{username='脱敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}

我们可以直接利用 sensitiveUser 去打印日志信息,而这个对象对于代码其他流程不影响,我们依然可以使用原来的 user 对象。

自定义脱敏策略生效的场景

默认情况下,我们指定的场景都是生效的。

但是你可能需要有些情况下不进行脱敏,比如有些用户密码为 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 时密码脱敏策略才会生效。

针对单个字段

上面的例子是基于注解式的编程,如果你只是单个字段。比如

  • singleSensitiveTest
@Test
public void singleSensitiveTest() {
    final String email = "[email protected]";
    IStrategy strategy = new StrategyEmail();
    final String emailSensitive = (String) strategy.des(email, null);
    System.out.println("脱敏后的邮箱:" + emailSensitive);
}
  • 日志信息
脱敏后的邮箱:123***@qq.com

属性为集合或者对象

如果某个属性是单个集合或者对象,则需要使用注解 @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()
}
  • 构建对象
/**
 * 构建用户-属性为列表,列表中为基础属性
 * @return 构建嵌套信息
 * @since 0.0.2
 */
public static UserEntryBaseType buildUserEntryBaseType() {
    UserEntryBaseType userEntryBaseType = new UserEntryBaseType();
    userEntryBaseType.setChineseNameList(Arrays.asList("盘古", "女娲", "伏羲"));
    userEntryBaseType.setChineseNameArray(new String[]{"盘古", "女娲", "伏羲"});
    return userEntryBaseType;
}
  • 测试演示
/**
 * 用户属性中有集合或者map,集合中属性是基础类型-脱敏测试
 * @since 0.0.2
 */
@Test
public void sensitiveEntryBaseTypeTest() {
    UserEntryBaseType userEntryBaseType = DataPrepareTest.buildUserEntryBaseType();
    System.out.println("脱敏前原始: " + userEntryBaseType);
    UserEntryBaseType sensitive = SensitiveUtil.desCopy(userEntryBaseType);
    System.out.println("脱敏对象: " + sensitive);
    System.out.println("脱敏后原始: " + userEntryBaseType);
}
  • 日志信息
脱敏前原始: UserEntryBaseType{chineseNameList=[盘古, 女娲, 伏羲], chineseNameArray=[盘古, 女娲, 伏羲]}
脱敏对象: UserEntryBaseType{chineseNameList=[*古, *娲, *羲], chineseNameArray=[*古, *娲, *羲]}
脱敏后原始: UserEntryBaseType{chineseNameList=[盘古, 女娲, 伏羲], chineseNameArray=[盘古, 女娲, 伏羲]}

放在对象属性上

  • 演示对象

这里的 User 和上面的 User 对象一致。

public class UserEntryObject {

    @SensitiveEntry
    private User user;

    @SensitiveEntry
    private List<User> userList;

    @SensitiveEntry
    private User[] userArray;
    
    //...
}
  • 对象构建
/**
 * 构建用户-属性为列表,数组。列表中为对象。
 * @return 构建嵌套信息
 * @since 0.0.2
 */
public static UserEntryObject buildUserEntryObject() {
    UserEntryObject userEntryObject = new UserEntryObject();
    User user = buildUser();
    User user2 = buildUser();
    User user3 = buildUser();
    userEntryObject.setUser(user);
    userEntryObject.setUserList(Arrays.asList(user2));
    userEntryObject.setUserArray(new User[]{user3});
    return userEntryObject;
}
  • 测试演示
/**
 * 用户属性中有集合或者对象,集合中属性是对象-脱敏测试
 * @since 0.0.2
 */
@Test
public void sensitiveEntryObjectTest() {
    UserEntryObject userEntryObject = DataPrepareTest.buildUserEntryObject();
    System.out.println("脱敏前原始: " + userEntryObject);
    UserEntryObject sensitiveUserEntryObject = SensitiveUtil.desCopy(userEntryObject);
    System.out.println("脱敏对象: " + sensitiveUserEntryObject);
    System.out.println("脱敏后原始: " + userEntryObject);
}
  • 测试结果
脱敏前原始: UserEntryObject{user=User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}, userList=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}], userArray=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}]}
脱敏对象: UserEntryObject{user=User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}, userList=[User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}], userArray=[User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}]}
脱敏后原始: UserEntryObject{user=User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}, userList=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}], userArray=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}]}

系统内置脱敏策略

引入原因

如果你看了前面的内容,会看到这样的代码:

@Sensitive(strategy = StrategyChineseName.class)
private String username;

但是这种需求很常见,就引入更简单的写法,如下:

@SensitiveStrategyChineseName
private String name;

和上面效果是一致的。

不足:无法灵活指定生效条件,下个版本准备解决这个问题。

系统内置注解

注解 等价 @Sensitive 备注
@SensitiveStrategyChineseName @Sensitive(strategy = StrategyChineseName.class) 中文名称脱敏
@SensitiveStrategyPassword @Sensitive(strategy = StrategyPassword.class) 密码脱敏
@SensitiveStrategyEmail @Sensitive(strategy = StrategyEmail.class) email 脱敏
@SensitiveStrategyCardId @Sensitive(strategy = StrategyCardId.class) 卡号脱敏
@SensitiveStrategyPhone @Sensitive(strategy = StrategyPhone.class) 手机号脱敏

使用案例

使用的方式和 @Sensitive 是一样的,只是一种简化,方便日常使用。

@SensitiveEntry 的结合和 @Sensitive 完全一致,此处不再演示。

  • SystemBuiltInAt.java

定义测试对象

@SensitiveStrategyPhone
    private String phone;

    @SensitiveStrategyPassword
    private String password;

    @SensitiveStrategyChineseName
    private String name;

    @SensitiveStrategyEmail
    private String email;

    @SensitiveStrategyCardId
    private String cardId;
    
    //Getter Setter
    //toString()
}
  • 对象构建
/**
 * 构建系统内置对象
 * @return 构建后的对象
 * @since 0.0.3
 */
public static SystemBuiltInAt buildSystemBuiltInAt() {
    SystemBuiltInAt systemBuiltInAt = new SystemBuiltInAt();
    systemBuiltInAt.setName("脱敏君");
    systemBuiltInAt.setPassword("1234567");
    systemBuiltInAt.setEmail("[email protected]");
    systemBuiltInAt.setCardId("123456190001011234");
    systemBuiltInAt.setPhone("18888888888");
    return systemBuiltInAt;
}
  • 测试方法

测试方法断言如下。

/**
 * 普通属性脱敏测试
 */
@Test
public void sensitiveTest() {
    final String originalStr = "SystemBuiltInAt{phone='18888888888', password='1234567', name='脱敏君', email='[email protected]', cardId='123456190001011234'}";
    final String sensitiveStr = "SystemBuiltInAt{phone='188****8888', password='null', name='脱*君', email='123**@qq.com', cardId='123456**********34'}";

    SystemBuiltInAt systemBuiltInAt = buildSystemBuiltInAt();
    Assert.assertEquals(originalStr, systemBuiltInAt.toString());

    SystemBuiltInAt sensitive = SensitiveUtil.desCopy(systemBuiltInAt);
    Assert.assertEquals(sensitiveStr, sensitive.toString());
    Assert.assertEquals(originalStr, systemBuiltInAt.toString());
}

与 @Sensitive 混合使用

如果你将新增的注解 @SensitiveStrategyChineseName@Sensitive 同时在一个字段上使用。

为了简化逻辑,优先选择执行 @Sensitive,如果 @Sensitive 执行脱敏, 那么 @SensitiveStrategyChineseName 将不会生效。

如:

/**
 * 测试字段
 * 1.当多种注解混合的时候,为了简化逻辑,优先选择 @Sensitive 注解。
 */
@SensitiveStrategyChineseName
@Sensitive(strategy = StrategyPassword.class)
private String testField;

自定义注解

  • 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,则不做处理
 *
 * @author dev-sxl
 * date 2020-09-14
 * @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;

    // 其他方法...
}

测试

/**
 * 用户属性中有集合或者对象,集合中属性是对象-脱敏测试
 * @since 0.0.11
 */
@Test
public void customSensitiveEntryObjectTest() {
    final String originalStr = "CustomUserEntryObject{user=User{username='脱敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}, userList=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}], userArray=[User{username='脱敏君', idCard='123456190001011234', password='1234567', email='[email protected]', phone='18888888888'}]}";
    final String sensitiveStr = "CustomUserEntryObject{user=User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}, userList=[User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}], userArray=[User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}]}";

    CustomUserEntryObject userEntryObject = DataPrepareTest.buildCustomUserEntryObject();
    Assert.assertEquals(originalStr, userEntryObject.toString());

    CustomUserEntryObject sensitiveUserEntryObject = SensitiveUtil.desCopy(userEntryObject);
    Assert.assertEquals(sensitiveStr, sensitiveUserEntryObject.toString());
    Assert.assertEquals(originalStr, userEntryObject.toString());
}

构建对象的方法如下:

/**
 * 构建用户-属性为列表,数组。列表中为对象。
 *
 * @return 构建嵌套信息
 * @since 0.0.11
 */
public static CustomUserEntryObject buildCustomUserEntryObject() {
    CustomUserEntryObject userEntryObject = new CustomUserEntryObject();
    User user = buildUser();
    User user2 = buildUser();
    User user3 = buildUser();
    userEntryObject.setUser(user);
    userEntryObject.setUserList(Arrays.asList(user2));
    userEntryObject.setUserArray(new User[]{user3});
    return userEntryObject;
}

/**
 * 构建测试用户对象
 *
 * @return 创建后的对象
 * @since 0.0.1
 */
public static User buildUser() {
    User user = new User();
    user.setUsername("脱敏君");
    user.setPassword("1234567");
    user.setEmail("[email protected]");
    user.setIdCard("123456190001011234");
    user.setPhone("18888888888");
    return user;
}

生成脱敏后的 JSON

说明

为了避免生成中间脱敏对象,v0.0.6 之后直接支持生成脱敏后的 JSON。

使用方法

新增工具类方法,可以直接返回脱敏后的 JSON。

生成的 JSON 是脱敏的,原对象属性值不受影响。

public static String desJson(Object object)

注解的使用方式

SensitiveUtil.desCopy() 完全一致。

使用示例代码

所有的测试案例中,都添加了对应的 desJson(Object) 测试代码,可以参考。

此处只展示最基本的使用。

@Test
public void sensitiveJsonTest() {
    final String originalStr = "SystemBuiltInAt{phone='18888888888', password='1234567', name='脱敏君', email='[email protected]', cardId='123456190001011234'}";
    final String sensitiveJson = "{\"cardId\":\"123456**********34\",\"email\":\"123**@qq.com\",\"name\":\"脱*君\",\"phone\":\"188****8888\"}";

    SystemBuiltInAt systemBuiltInAt = DataPrepareTest.buildSystemBuiltInAt();
    Assert.assertEquals(sensitiveJson, SensitiveUtil.desJson(systemBuiltInAt));
    Assert.assertEquals(originalStr, systemBuiltInAt.toString());
}

注意

本次 JSON 脱敏基于 FastJSON

FastJSON 在序列化本身存在一定限制。当对象中有集合,集合中还是对象时,结果不尽如人意。

示例代码

本测试案例可见测试代码。

@Test
public void sensitiveUserCollectionJsonTest() {
    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\":\"123**@qq.com\",\"idCard\":\"123456**********34\",\"phone\":\"188****8888\",\"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)

针对集合的处理

v0.0.7 支持的新特性,便于用户处理集合相关的脱敏。

如果列表为空,则直接返回空列表。

更多测试代码参见 SensitiveUtilCollectionTest.java

集合脱敏-对象拷贝

  • List desCopyCollection(Collection collection)

返回脱敏后的对象集合

List<User> userList = DataPrepareTest.buildUserList();
List<User> sensitiveList = SensitiveUtil.desCopyCollection(userList);
Assert.assertEquals("[User{username='脱*君', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}, User{username='集**试', idCard='123456**********34', password='null', email='123**@qq.com', phone='188****8888'}]", sensitiveList.toString());

集合脱敏-json

  • List desJsonCollection(Collection<?> collection)

返回脱敏后的 json 列表

List<User> userList = DataPrepareTest.buildUserList();

List<String> sensitiveJsonList = SensitiveUtil.desJsonCollection(userList);
Assert.assertEquals("[{\"email\":\"123**@qq.com\",\"idCard\":\"123456**********34\",\"phone\":\"188****8888\",\"username\":\"脱*君\"}, {\"email\":\"123**@qq.com\",\"idCard\":\"123456**********34\",\"phone\":\"188****8888\",\"username\":\"集**试\"}]", sensitiveJsonList.toString());

脱敏引导类

为了配置的灵活性,引入了引导类。

核心 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 种基于序列化实现的深度复制,便于用户替换使用。

每一种都可以单独使用,保证依赖更加轻量。

自定义

为满足不同场景的需求,深度复制策略支持用户自定义。

自定义深度复制

需求 & BUGS

issues

欢迎加入开发

如果你对本项目有兴趣,并且对代码有一定追求,可以申请加入本项目开发。

如果你善于写文档,或者愿意补全测试案例,也非常欢迎加入。

ROAD-MAP

  • [ ] 考虑添加针对 MAP 的脱敏支持

  • [ ] 考虑对 JSON 的可拓展性,不拘泥于 FastJSON

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].