- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
日志脱敏是常见的安全需求。普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦.
sensitive 提供了基于注解的方式,并且内置了常见的脱敏方式,便于开发.
为了金融交易的安全性,国家强制规定对于以下信息是要日志脱敏的:
用户名 。
手机号 。
邮箱 。
银行卡号 。
密码 。
身份证号 。
存储的时候上面的信息都需要加密,密码为不可逆加密,其他为可逆加密.
类似的功能有很多。不在本系统的解决范围内.
基于注解的日志脱敏.
可以自定义策略实现,策略生效条件.
内置常见的十几种脱敏内置方案.
java 深拷贝,且原始对象不用实现任何接口.
支持用户自定义注解.
支持基于 FastJSON 直接生成脱敏后的 json 。
变更日志 。
JDK 7+ 。
Maven 3.x 。
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-core</artifactId>
<version>1.0.0</version>
</dependency>
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 打印日志信息.
@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();
}
如果你将新增的注解 @SensitiveStrategyChineseName 与 @Sensitive 同时在一个字段上使用.
为了简化逻辑,优先选择执行 @Sensitive ,如果 @Sensitive 执行脱敏, 那么 @SensitiveStrategyChineseName 将不会生效.
如:
/**
* 测试字段
* 1.当多种注解混合的时候,为了简化逻辑,优先选择 @Sensitive 注解。
*/
@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<String> chineseNameList;
@SensitiveEntry
@Sensitive(strategy = StrategyChineseName.class)
private String[] chineseNameArray;
//Getter & Setter & toString()
}
例子如下:
public class UserEntryObject {
@SensitiveEntry
private User user;
@SensitiveEntry
private List<User> userList;
@SensitiveEntry
private User[] userArray;
//...
}
/**
* 自定义密码脱敏策略
* @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{
}
@SensitiveStrategy 策略单独使用的时候,默认是生效的.
如果有 @SensitiveCondition 注解,则只有当条件满足时,才会执行脱敏策略.
@SensitiveCondition 只会对系统内置注解和自定义注解生效,因为 @Sensitive 有属于自己的策略生效条件.
@Sensitive 优先生效,然后是系统内置注解,最后是用户自定义注解.
两个元注解 @SensitiveStrategy 、 @SensitiveCondition 分别指定了对应的实现.
public class CustomPasswordStrategy implements IStrategy {
@Override
public Object des(Object original, IContext context) {
return "**********************";
}
}
/**
* 让这些 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;
}
可以根据自己的业务需要,在自定义的注解上使用 @SensitiveEntry .
使用方式保持和 @SensitiveEntry 一样即可.
/**
* 级联脱敏注解,如果对象中属性为另外一个对象(集合),则可以使用这个注解指定。
* <p>
* 1. 如果属性为 Iterable 的子类集合,则当做列表处理,遍历其中的对象
* 2. 如果是普通对象,则处理对象中的脱敏信息
* 3. 如果是普通字段/MAP,则不做处理
* @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;
// 其他方法...
}
为了避免生成中间脱敏对象,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 .
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) .
为了配置的灵活性,引入了引导类.
SensitiveBs 引导类的核心方法列表如下:
序号 | 方法 | 参数 | 结果 | 说明 |
---|---|---|---|---|
1 | desCopy() | 目标对象 | 深度拷贝脱敏对象 | 适应性更强 |
2 | desJson() | 目标对象 | 脱敏对象 json | 性能较好 |
使用方式和工具类一致,示意如下:
SensitiveBs.newInstance().desCopy(user);
默认的使用 FastJson 进行对象的深度拷贝,等价于:
SensitiveBs.newInstance()
.deepCopy(FastJsonDeepCopy.getInstance())
.desJson(user);
参见 SensitiveBsTest.java 。
deepCopy 用于指定深度复制的具体实现,支持用户自定义.
深度复制可以保证我们日志输出对象脱敏,同时不影响正常业务代码的使用.
可以实现深度复制的方式有很多种,默认基于 fastjson 实现的.
为保证后续良性发展,v0.0.13 版本之后将深度复制接口抽离为单独的项目:
deep-copy 。
目前支持 6 种基于序列化实现的深度复制,便于用户替换使用.
每一种都可以单独使用,保证依赖更加轻量.
为满足不同场景的需求,深度复制策略支持用户自定义.
自定义深度复制 。
https://github.com/houbb/sensitive 。
最后此篇关于金融用户敏感数据如何优雅地实现脱敏?的文章就讲到这里了,如果你想了解更多关于金融用户敏感数据如何优雅地实现脱敏?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我正在遍历自定义数据对象列表,并以可分页的格式将数据输出到屏幕。每个项目都需要提供编辑详细信息(重定向到另一个 View )和删除数据的能力。为此,我需要传递某种 id 来标识将要更新或删除的记录。我
我们正在使用 React-Native 构建移动应用程序,需要将一些敏感数据(例如客户端 key /密码)嵌入到移动应用程序本身中。 在安全性方面(最难进行逆向工程),标准做法是什么?数据应该在 na
我们已经知道使用 char[] 而不是 String 来处理敏感数据的建议/做法。有多种原因。一种是在不再需要敏感数据后立即清理它们: char[] passwd = passwordProvider
我有一些敏感数据想在使用后直接清除。目前,敏感数据的形式是 NSString。 NSString 在我的理解中是不可变的,这意味着我无法真正清除数据。不过,NSMutableString 似乎更合适,
我想知道如何推送到我的 Github 存储库(我需要公开该存储库,以便向雇主展示),而不显示敏感信息。我正在使用 Eclipse/Java。 我制作了一个在 Chrome 中使用 Selenium w
我是一名优秀的程序员,十分优秀!