金融用户敏感数据如何优雅地实现脱敏? 每日热讯
日志脱敏是常见的安全需求。普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦。
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 集合 |
通过注解,指定每一个字段的脱敏策略。
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) | 指定范围脱敏 |
@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.javapublic 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.javapublic 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]
Referenceshttps://github.com/houbb/sensitive [7]。
[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。
标签:
精彩推送
韩国KOSPI指数6月2日(周五)开盘上涨18.22点,涨幅0.71%,报2587.39点|动态
韩国KOSPI指数6月2日(周五)开盘上涨18 22点,涨幅0 71%,报2587 39点
iPhonex呼叫失败是什么原因(苹果x老是呼叫失败原因) 全球简讯
iphonex呼叫失败是什么原因,iphonex呼叫失败是因为信号或电话运营商问题,如果是信号问题,可以将手机重启
天猫奢品 618 开门红战报:12 个品牌 30 分钟成交超去年全天
【亿邦原创】5月31日晚8点天猫618正式开卖,各大奢侈品牌官方旗舰店在天猫奢品表现亮眼,Burberry、Chloe、
新闻快讯
X 关闭
X 关闭
新闻快讯
- 金融用户敏感数据如何优雅地实现脱敏? 每日热讯
- 健身去哪儿?官方方案来了!_速读
- 砍掉“Hey”,苹果宣布iPhone/iPad语音助手唤醒词改为“Siri” 环球快看点
- 【全球快播报】国际金融市场早知道:6月6日
- 马斯克北京吃饭菜单价格曝光:22人花了45000多元
- 走路外八字怎么矫正_走路外八怎么矫正
- 快资讯丨什么方法可快速止牙痛_什么方法快速止牙痛
- wlan是什么意思(WiFi和WLAN有什么区别)-环球视点
- 安庆旺旺食品有限公司
- 冻了的小土豆怎么炒好吃?|微头条
- 盾山一技能怎么能快速卡bug_盾山一技能怎么变大
- 漯河医疗损害纠纷律师收费标准是多少
- 语音系统用上全自研算法!李想:95%以上控制张嘴就能搞定
- 环球要闻:雅居乐集团(03383)5月预售金额42亿元
- 3连板利通电子:本次拟投资事项涉及新业务领域 公司尚无开展相关业务的经验
- 蒙牛乳业(02319)6月5日斥资1073.8万港元回购35万股
- 鞍钢股份:一季度国际钢材供应偏紧 公司出口订货量实现增长|每日热闻
- 广西出台防“烂尾楼”新规,房企获预售许可前不得开展销售活动-当前简讯
- 暴雨,雷暴大风,强降雨来袭,气温超35℃!这些地方,高温持续日数同期罕见
- 热推荐:延吉市公安局交警大队河北一中队开展执勤车辆安全行驶教育培训
- 6月5日 11:25分 捷邦科技(301326)股价快速拉升
- 藏书楼(关于藏书楼的基本详情介绍)
- 车上4人死亡 杭州一台电动车碰撞后起火
- 地下城卡片合成找谁_地下城卡片在哪合成_新要闻
- 贞丰县:15万亩四月李俏销省外市场
- 焦点速递!突发!小飞机闯入华盛顿 美军出动F-16紧急拦截
- 当前资讯!我国连接器市场达到290亿美元,智能汽车发展助力行业快速提升
- 碧云天黄花地西风紧北雁南飞解释_碧云天黄花地西风紧北雁南飞
- 360智脑大模型应用发布会将于6月13日举办 全球观天下
- 最新:杨冕(关于杨冕的简介)
- 世界滚动:网友发布视频《三亚被宰记》,三亚通报调查情况
- 全市“三夏”生产工作调度会召开-世界今亮点
- 热门:微信群名片(群名片)
- 湖口县气象台更新雷电黄色预警信号【III级/较重】【2023-06-05】
- 每日看点!介绍自己优势的一段话_介绍自己优势
- 新漫评:驴象“斗气”极限拉扯 美债“炸弹”引信难拆
- 止痛泵和止痛针哪个效果好_止痛泵 看点
- 团队名称创意_团队名
- 过年要回家了的说说_过年放假回家说说 回家的心情有什么句子表达 每日热议
- 新摩卡:横向对比优势明显 销量有望就此提振_天天快看
- 积成电子最新公告:预中标1.05亿元国家电网采购项目-环球聚看点
- 美媒:无论美国多么努力,都将失败
- 当前通讯!青瓜的功效与作用
- 全面屏iPhone要来了!苹果新专利可设计屏下Face ID_焦点速看
- survivalproject个人汉化补丁发布 世界播报
- 天津西青区可提供奥克斯冰箱维修服务地址在哪 世界热文
- 焦点日报:斗罗大陆:唐三现存一株仙草,看清属性,毒斗罗不敢要!
- 视频|夏天的甜蜜,藏在望城的这些水果里
- 世界今亮点!买保险买哪个保险公司的好?方式有哪些?
- 摩字组词有什么_摩字组词 天天时讯