文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-11-30 13:00

关注

项目介绍

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

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

日志脱敏

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

  1. 用户名
  2. 手机号
  3. 邮箱
  4. 银行卡号
  5. 密码
  6. 身份证号

特性

  1. 基于注解的日志脱敏。
  2. 可以自定义策略实现,策略生效条件。
  3. 内置常见的十几种脱敏内置方案。
  4. java 深拷贝,且原始对象不用实现任何接口。
  5. 支持用户自定义注解。
  6. 支持基于 FastJSON 直接生成脱敏后的 json。

快速开始

环境准备

maven 导入


    com.github.houbb
    sensitive-core
    1.0.0

核心 api 简介

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

序号

方法

参数

结果

说明

1

desCopy()

目标对象

深度拷贝脱敏对象

适应性更强

2

desJson()

目标对象

脱敏对象 json

性能较好

3

desCopyCollection()

目标对象集合

深度拷贝脱敏对象集合


4

desJsonCollection()

目标对象集合

脱敏对象 json 集合


定义对象

通过注解,指定每一个字段的脱敏策略。

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)

身份证脱敏

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)

护照脱敏

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 {
    
    Class condition() default ConditionAlwaysTrue.class;
    
    Class strategy();


}

与 @Sensitive 混合使用

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

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

如:


@SensitiveStrategyChineseName
@Sensitive(strategy = StrategyPassword.class)
private String testField;

更多特性

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

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

但是你可能需要有些情况下不进行脱敏,比如有些用户密码为 123456,你觉得这种用户不脱敏也罢。

@Sensitive(condition = ConditionFooPassword.class, strategy = StrategyPassword.class)
private String password;

其他保持不变,我们指定了一个 condition,实现如下:

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

会遍历每一个属性,执行上面的脱敏策略。

会处理对象中各个字段上的脱敏注解信息。

遍历每一个对象,处理对象中各个字段上的脱敏注解信息。

放在集合属性上,且属性为普通对象

作为演示,集合中为普通的字符串。

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;
    //...
}

自定义注解

案例1

自定义密码脱敏策略&自定义密码脱敏策略生效条件


@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveStrategy(CustomPasswordStrategy.class)
public @interface SensitiveCustomPasswordStrategy {
}

@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveCondition(ConditionFooPassword.class)
public @interface SensitiveCustomPasswordCondition{
}

@SensitiveStrategy 策略单独使用的时候,默认是生效的。

如果有 @SensitiveCondition 注解,则只有当条件满足时,才会执行脱敏策略。

@SensitiveCondition 只会对系统内置注解和自定义注解生效,因为 @Sensitive 有属于自己的策略生效条件。

@Sensitive 优先生效,然后是系统内置注解,最后是用户自定义注解。

对应的实现

两个元注解 @SensitiveStrategy@SensitiveCondition 分别指定了对应的实现。

public class CustomPasswordStrategy implements IStrategy {
    @Override
    public Object des(Object original, IContext context) {
        return "**********************";
    }
}

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());
}

构建对象的方法如下:


private CustomPasswordModel buildCustomPasswordModel(){
    CustomPasswordModel model = new CustomPasswordModel();
    model.setPassword("hello");
    model.setFooPassword("123456");
    return model;
}

案例2

自定义级联脱敏注解

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

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


@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@SensitiveEntry
public @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。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯