金融用户敏感数据如何优雅地实现脱敏? 每日热讯

时间:2023-06-06 10:47:41 来源: 今日头条
项目介绍

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

sensitive[1]提供了基于注解的方式,并且内置了常见的脱敏方式,便于开发。

日志脱敏

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


(资料图片)

用户名手机号邮箱银行卡号密码身份证号。特性基于注解的日志脱敏。可以自定义策略实现,策略生效条件。内置常见的十几种脱敏内置方案。java 深拷贝,且原始对象不用实现任何接口。支持用户自定义注解。支持基于 FastJSON 直接生成脱敏后的 json。快速开始环境准备JDK 7+Maven 3.xmaven 导入
    com.github.houbb    sensitive-core    1.0.0
核心 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 condition() default ConditionAlwaysTrue.class;    /**     * 执行的策略     * @return 策略对应的类型     */    Class 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 chineseNameList;    @SensitiveEntry    @Sensitive(strategy = StrategyChineseName.class)    private String[] chineseNameArray;    //Getter & Setter & toString()}
放在对象属性上

例子如下:

public class UserEntryObject {    @SensitiveEntry    private User user;    @SensitiveEntry    private List 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;    //其他方法}
测试
/** * 自定义注解测试 */@Testpublic 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;}
案例2v0.0.11 新增功能。允许功能自定义级联脱敏注解。自定义级联脱敏注解自定义级联脱敏注解

可以根据自己的业务需要,在自定义的注解上使用@SensitiveEntry。

使用方式保持和@SensitiveEntry一样即可。

/** * 级联脱敏注解,如果对象中属性为另外一个对象(集合),则可以使用这个注解指定。 * 

* 1. 如果属性为 Iterable 的子类集合,则当做列表处理,遍历其中的对象 * 2. 如果是普通对象,则处理对象中的脱敏信息 * 3. 如果是普通字段/MAP,则不做处理 * @since 0.0.11 */@Inherited@Documented@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@SensitiveEntrypublic @interface SensitiveEntryCustom {}

定义测试对象

定义一个使用自定义注解的对象。

public class CustomUserEntryObject {    @SensitiveEntryCustom    private User user;    @SensitiveEntryCustom    private List 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[2]。

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[3]。

deepCopy 用于指定深度复制的具体实现,支持用户自定义。

深度复制(DeepCopy)说明

深度复制可以保证我们日志输出对象脱敏,同时不影响正常业务代码的使用。

可以实现深度复制的方式有很多种,默认基于fastjson[4]实现的。

为保证后续良性发展,v0.0.13 版本之后将深度复制接口抽离为单独的项目:

deep-copy[5]

内置策略

目前支持 6 种基于序列化实现的深度复制,便于用户替换使用。

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

自定义

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

自定义深度复制[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] 自定义深度复制:https://github.com/houbb/deep-copy#自定义。

[7] https://github.com/houbb/sensitive :https://github.com/houbb/sensitive。

标签:

精彩推送

市司法局法治教育进校园 护航青春助成长

“同学们,你们了解校园霸凌吗?”“如果你遇到校园霸凌会怎么做?”法治讲堂在热烈的互动问答中开始了...

来源:2023.06.02

韩国KOSPI指数6月2日(周五)开盘上涨18.22点,涨幅0.71%,报2587.39点|动态

韩国KOSPI指数6月2日(周五)开盘上涨18 22点,涨幅0 71%,报2587 39点

来源:2023.06.02

扬州运河博物馆门票怎么预约

预约入口:中运博官网中运博实行实名制网络预约、分时段参观政策,观众可提前7天通过“中国大运河博物馆...

来源:2023.06.02

骁龙435和骁龙439_骁龙435

1、能是能,但是很卡。2、骁龙435处理器采用28nmLP工艺制造,八核心Cortex-A53架构,主频1 4GHz,

来源:2023.06.02

iPhonex呼叫失败是什么原因(苹果x老是呼叫失败原因) 全球简讯

iphonex呼叫失败是什么原因,iphonex呼叫失败是因为信号或电话运营商问题,如果是信号问题,可以将手机重启

来源:2023.06.02

南京将发放买车消费补贴 买新车最高补贴5000元!

南京将发放买车消费补贴买新车最高补贴5000元!

来源:2023.06.02

龙山首届家庭文化节开幕

龙山首届家庭文化节开幕吾爱吾家,互助更佳,日前,龙山镇首届家庭文化节开幕,“最美家”互助联盟正式...

来源:2023.06.02

火炬之光无限无法连接服务器(解决办法一览)_新视野

导读综合小编来为大家讲解下火炬之光无限无法连接服务器,解决办法一览这个很多人还不知道,现在让我们一起

来源:2023.06.02

天猫奢品 618 开门红战报:12 个品牌 30 分钟成交超去年全天

【亿邦原创】5月31日晚8点天猫618正式开卖,各大奢侈品牌官方旗舰店在天猫奢品表现亮眼,Burberry、Chloe、

来源:2023.06.02

新闻快讯

X 关闭

X 关闭

新闻快讯