Sensitive
🔐Sensitive log tool for java, based on java annotation. (基于注解的 java 日志脱敏工具框架,更加优雅的日志打印。支持自定义哈希、支持基于 log4j2 插件的统一脱敏、支持 logback 插件统一脱敏)
Install / Use
/learn @houbb/SensitiveREADME
项目介绍
日志脱敏是常见的安全需求。普通的基于工具类方法的方式,对代码的入侵性太强,编写起来又特别麻烦。
sensitive 项目提供基于注解的方式,并且内置了常见的脱敏方式,便于开发。
支持 logback 和 log4j2 等常见的日志脱敏插件。
日志插件解决正则匹配长文本可能出现的回溯问题,性能远超正则。
日志脱敏
为了金融交易的安全性,国家强制规定对于以下信息是要日志脱敏的:
-
用户名
-
手机号
-
邮箱
-
银行卡号
-
密码
-
身份证号
持久化加密
存储的时候上面的信息都需要加密,密码为不可逆加密,其他为可逆加密。
类似的功能有很多。不在本系统的解决范围内。
特性
-
基于注解的日志脱敏。
-
可以自定义策略实现,策略生效条件。
-
内置常见的十几种脱敏内置方案。
-
java 深拷贝,且原始对象不用实现任何接口。
6. 支持基于 FastJSON 直接生成脱敏后的 json
项目推荐
下面是一些日志、加解密、脱敏安全相关的库推荐:
| 项目 | 介绍 | |:----------------------------------------------------------------------|:----------------------| | sensitive-word | 高性能敏感词核心库 | | sensitive-word-admin | 敏感词控台,前后端分离 | | sensitive | 高性能日志脱敏组件 | | auto-log | 统一日志切面组件,支持全链路traceId | | encryption-local | 离线加密机组件 | | encryption | 加密机标准API+本地客户端 | | encryption-server | 加密机服务 |
变更日志
v-1.6.0 新特性
- 添加 logback 脱敏插件
拓展阅读
日志开源组件(一)java 注解结合 spring aop 实现自动输出日志
日志开源组件(二)java 注解结合 spring aop 实现日志traceId唯一标识
日志开源组件(三)java 注解结合 spring aop 自动输出日志新增拦截器与过滤器
日志开源组件(四)如何动态修改 spring aop 切面信息?让自动日志输出框架更好用
日志开源组件(五)如何将 dubbo filter 拦截器原理运用到日志拦截器中?
日志开源组件(六)Adaptive Sampling 自适应采样
高性能日志脱敏组件(一)java 日志脱敏框架 sensitive,优雅的打印脱敏日志
高性能日志脱敏组件(二)金融用户敏感数据如何优雅地实现脱敏?
高性能日志脱敏组件(三)日志脱敏之后,无法根据信息快速定位怎么办?
高性能日志脱敏组件(四)基于 log4j2 插件实现统一日志脱敏,性能远超正则替换
高性能日志脱敏组件(五)已支持 log4j2 和 logback 插件
快速开始
环境准备
JDK 1.8+
Maven 3.x
maven 导入
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-core</artifactId>
<version>1.7.0</version>
</dependency>
核心 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<? 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 pa
