{"id":13694703,"url":"https://github.com/houbb/sensitive","last_synced_at":"2025-04-12T23:40:01.675Z","repository":{"id":37396894,"uuid":"163493667","full_name":"houbb/sensitive","owner":"houbb","description":"🔐Sensitive log tool for java, based on java annotation. (基于注解的 java 日志脱敏工具框架，更加优雅的日志打印。支持自定义哈希、支持基于 log4j2 插件的统一脱敏、支持 logback 插件统一脱敏)","archived":false,"fork":false,"pushed_at":"2024-03-23T03:52:05.000Z","size":462,"stargazers_count":637,"open_issues_count":8,"forks_count":182,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-12T23:39:56.984Z","etag":null,"topics":["dfa","fastjson","java","java-annotation","json","log","log4j2","log4j2-plugin","logback","security","sensitive","sensitive-data-security","slf4j"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/houbb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGE_LOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-29T08:39:58.000Z","updated_at":"2025-04-11T09:34:46.000Z","dependencies_parsed_at":"2024-03-23T04:32:23.802Z","dependency_job_id":"16c4ba92-b6df-4a90-835b-61a3a8ab43d0","html_url":"https://github.com/houbb/sensitive","commit_stats":{"total_commits":114,"total_committers":6,"mean_commits":19.0,"dds":"0.39473684210526316","last_synced_commit":"bbd27de6f31fa7a2684863bd8285c5f146f3b8cd"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/houbb%2Fsensitive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/houbb","download_url":"https://codeload.github.com/houbb/sensitive/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248647254,"owners_count":21139081,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dfa","fastjson","java","java-annotation","json","log","log4j2","log4j2-plugin","logback","security","sensitive","sensitive-data-security","slf4j"],"created_at":"2024-08-02T17:01:37.968Z","updated_at":"2025-04-12T23:40:01.652Z","avatar_url":"https://github.com/houbb.png","language":"Java","readme":"# 项目介绍\n\n日志脱敏是常见的安全需求。普通的基于工具类方法的方式，对代码的入侵性太强，编写起来又特别麻烦。\n\n[sensitive](https://github.com/houbb/sensitive) 项目提供基于注解的方式，并且内置了常见的脱敏方式，便于开发。\n\n支持 logback 和 log4j2 等常见的日志脱敏插件。\n\n**日志插件解决正则匹配长文本可能出现的回溯问题，性能远超正则**。\n\n[![Build Status](https://travis-ci.com/houbb/sensitive.svg?branch=master)](https://travis-ci.com/houbb/sensitive)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.houbb/sensitive/badge.svg)](http://mvnrepository.com/artifact/com.github.houbb/sensitive)\n[![](https://img.shields.io/badge/license-Apache2-FF0080.svg)](https://github.com/houbb/sensitive/blob/master/LICENSE.txt)\n[![Open Source Love](https://badges.frapsoft.com/os/v2/open-source.svg?v=103)](https://github.com/houbb/sensitive)\n\n## 日志脱敏\n\n为了金融交易的安全性，国家强制规定对于以下信息是要日志脱敏的：\n\n1. 用户名\n\n2. 手机号\n\n3. 邮箱\n\n4. 银行卡号\n\n5. 密码\n\n6. 身份证号\n\n## 持久化加密\n\n存储的时候上面的信息都需要加密，密码为不可逆加密，其他为可逆加密。\n\n类似的功能有很多。不在本系统的解决范围内。\n\n# 特性\n\n1. 基于注解的日志脱敏。\n\n2. 可以自定义策略实现，策略生效条件。\n\n3. 内置常见的十几种脱敏内置方案。\n\n4. java 深拷贝，且原始对象不用实现任何接口。\n\n[5. 支持用户自定义注解。](https://github.com/houbb/sensitive#%E8%87%AA%E5%AE%9A%E4%B9%89%E6%B3%A8%E8%A7%A3)\n\n[6. 支持基于 FastJSON 直接生成脱敏后的 json](https://github.com/houbb/sensitive#%E7%94%9F%E6%88%90%E8%84%B1%E6%95%8F%E5%90%8E%E7%9A%84-json)\n\n[7. 支持自定义哈希策略，更加方便定位日志问题](https://github.com/houbb/sensitive#%E9%85%8D%E7%BD%AE%E5%93%88%E5%B8%8C%E7%AD%96%E7%95%A5)\n\n[8. 支持基于 log4j2 的统一脱敏策略](https://github.com/houbb/sensitive#log4j2-%E6%8F%92%E4%BB%B6%E7%BB%9F%E4%B8%80%E8%84%B1%E6%95%8F)\n\n[9. 支持基于 logback 的统一脱敏策略](https://github.com/houbb/sensitive#logback-%E8%84%B1%E6%95%8F%E6%8F%92%E4%BB%B6)\n\n## 变更日志\n\n\u003e [变更日志](https://github.com/houbb/sensitive/blob/master/CHANGE_LOG.md)\n\n### v-1.6.0 新特性\n\n- 添加 logback 脱敏插件\n\n## 拓展阅读\n\n[日志开源组件（一）java 注解结合 spring aop 实现自动输出日志](https://houbb.github.io/2023/08/06/auto-log-01-overview)\n\n[日志开源组件（二）java 注解结合 spring aop 实现日志traceId唯一标识](https://houbb.github.io/2023/08/06/auto-log-02-trace-id)\n\n[日志开源组件（三）java 注解结合 spring aop 自动输出日志新增拦截器与过滤器](https://houbb.github.io/2023/08/06/auto-log-03-filter)\n\n[日志开源组件（四）如何动态修改 spring aop 切面信息？让自动日志输出框架更好用](https://houbb.github.io/2023/08/06/auto-log-04-dynamic-aop)\n\n[日志开源组件（五）如何将 dubbo filter 拦截器原理运用到日志拦截器中？](https://houbb.github.io/2023/08/06/auto-log-05-dubbo-interceptor)\n\n[日志开源组件（六）Adaptive Sampling 自适应采样](https://mp.weixin.qq.com/s/9JH3WfR6Y474LCbY2mZxZQ)\n\n[高性能日志脱敏组件（一）java 日志脱敏框架 sensitive，优雅的打印脱敏日志](https://mp.weixin.qq.com/s/xzQNDF7s705iurk7N0uheQ)\n\n[高性能日志脱敏组件（二）金融用户敏感数据如何优雅地实现脱敏？](https://mp.weixin.qq.com/s/ljChFiNLzV6GLaUDjehA0Q)\n\n[高性能日志脱敏组件（三）日志脱敏之后，无法根据信息快速定位怎么办？](https://mp.weixin.qq.com/s/tZqOH_8QTKrD1oaclNoewg)\n\n[高性能日志脱敏组件（四）基于 log4j2 插件实现统一日志脱敏，性能远超正则替换](https://mp.weixin.qq.com/s/ZlWRqT7S92aXFuy-l9Uh3Q)\n\n[高性能日志脱敏组件（五）已支持 log4j2 和 logback 插件](https://mp.weixin.qq.com/s/3ARK6PW7pyUhAbO2ctnndg)\n\n# 快速开始\n\n## 环境准备\n\nJDK 1.8+\n\nMaven 3.x\n\n## maven 导入\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.houbb\u003c/groupId\u003e\n    \u003cartifactId\u003esensitive-core\u003c/artifactId\u003e\n    \u003cversion\u003e1.7.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## 核心 api 简介\n\n`SensitiveUtil` 工具类的核心方法列表如下：\n\n| 序号 | 方法 | 参数 | 结果 | 说明 |\n|:---|:---|:---|:---|:---|\n| 1 | desCopy() | 目标对象 | 深度拷贝脱敏对象 | 适应性更强 |\n| 2 | desJson() | 目标对象 | 脱敏对象 json | 性能较好 |\n| 3 | desCopyCollection() | 目标对象集合 | 深度拷贝脱敏对象集合 | |\n| 4 | desJsonCollection() | 目标对象集合 | 脱敏对象 json 集合 | |\n\n## 定义对象\n\n- UserAnnotationBean.java\n\n通过注解，指定每一个字段的脱敏策略。\n\n```java\npublic class UserAnnotationBean {\n\n    @SensitiveStrategyChineseName\n    private String username;\n\n    @SensitiveStrategyPassword\n    private String password;\n\n    @SensitiveStrategyPassport\n    private String passport;\n\n    @SensitiveStrategyIdNo\n    private String idNo;\n\n    @SensitiveStrategyCardId\n    private String bandCardId;\n\n    @SensitiveStrategyPhone\n    private String phone;\n\n    @SensitiveStrategyEmail\n    private String email;\n\n    @SensitiveStrategyAddress\n    private String address;\n\n    @SensitiveStrategyBirthday\n    private String birthday;\n\n    @SensitiveStrategyGps\n    private String gps;\n\n    @SensitiveStrategyIp\n    private String ip;\n\n    @SensitiveStrategyMaskAll\n    private String maskAll;\n\n    @SensitiveStrategyMaskHalf\n    private String maskHalf;\n\n    @SensitiveStrategyMaskRange\n    private String maskRange;\n\n    //Getter \u0026 Setter\n    //toString()\n}\n```\n\n- 数据准备\n\n构建一个最简单的测试对象：\n\n```java\nUserAnnotationBean bean  = new UserAnnotationBean();\nbean.setUsername(\"张三\");\nbean.setPassword(\"123456\");\nbean.setPassport(\"CN1234567\");\nbean.setPhone(\"13066668888\");\nbean.setAddress(\"中国上海市浦东新区外滩18号\");\nbean.setEmail(\"whatanice@code.com\");\nbean.setBirthday(\"20220831\");\nbean.setGps(\"66.888888\");\nbean.setIp(\"127.0.0.1\");\nbean.setMaskAll(\"可恶啊我会被全部掩盖\");\nbean.setMaskHalf(\"还好我只会被掩盖一半\");\nbean.setMaskRange(\"我比较灵活指定掩盖范围\");\nbean.setBandCardId(\"666123456789066\");\nbean.setIdNo(\"360123202306018888\");\n```\n\n- 测试代码\n\n```\nfinal 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='我比较灵活指定掩盖范围'}\";\nfinal 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='我*********围'}\";\nfinal 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\\\":\\\"张*\\\"}\";\n\nUserAnnotationBean sensitiveUser = SensitiveUtil.desCopy(bean);\nAssert.assertEquals(sensitiveStr, sensitiveUser.toString());\nAssert.assertEquals(originalStr, bean.toString());\n\nString sensitiveJson = SensitiveUtil.desJson(bean);\nAssert.assertEquals(expectSensitiveJson, sensitiveJson);\n```\n\n我们可以直接利用 `sensitiveUser` 去打印日志信息，而这个对象对于代码其他流程不影响，我们依然可以使用原来的 `user` 对象。\n\n当然，也可以使用 `sensitiveJson` 打印日志信息。\n\n# @Sensitive 注解\n\n## 说明\n\n`@SensitiveStrategyChineseName` 这种注解是为了便于用户使用，本质上等价于 `@Sensitive(strategy = StrategyChineseName.class)`。\n\n`@Sensitive` 注解可以指定对应的脱敏策略。\n\n## 内置注解与映射\n\n| 编号 | 注解                              | 等价 @Sensitive                                      | 备注       |\n|:---|:--------------------------------|:---------------------------------------------------|:---------|\n| 1  | `@SensitiveStrategyChineseName` | `@Sensitive(strategy = StrategyChineseName.class)` | 中文名称脱敏   |\n| 2  | `@SensitiveStrategyPassword`    | `@Sensitive(strategy = StrategyPassword.class)`    | 密码脱敏     |\n| 3  | `@SensitiveStrategyEmail`       | `@Sensitive(strategy = StrategyEmail.class)`       | email 脱敏 |\n| 4  | `@SensitiveStrategyCardId`      | `@Sensitive(strategy = StrategyCardId.class)`      | 卡号脱敏     |\n| 5  | `@SensitiveStrategyPhone`       | `@Sensitive(strategy = StrategyPhone.class)`       | 手机号脱敏    |\n| 6  | `@SensitiveStrategyIdNo`        | `@Sensitive(strategy = StrategyIdNo.class)`        | 身份证脱敏    |\n| 6  | `@SensitiveStrategyAddress`     | `@Sensitive(strategy = StrategyAddress.class)`     | 地址脱敏     |\n| 7  | `@SensitiveStrategyGps`         | `@Sensitive(strategy = StrategyGps.class)`     | GPS 脱敏   |\n| 8  | `@SensitiveStrategyIp`          | `@Sensitive(strategy = StrategyIp.class)`     | IP 脱敏    |\n| 9  | `@SensitiveStrategyBirthday`    | `@Sensitive(strategy = StrategyBirthday.class)`     | 生日脱敏     |\n| 10 | `@SensitiveStrategyPassport`    | `@Sensitive(strategy = StrategyPassport.class)`     | 护照脱敏     |\n| 11 | `@SensitiveStrategyMaskAll`     | `@Sensitive(strategy = StrategyMaskAll.class)`     | 全部脱敏     |\n| 12 | `@SensitiveStrategyMaskHalf`    | `@Sensitive(strategy = StrategyMaskHalf.class)`     | 一半脱敏     |\n| 13 | `@SensitiveStrategyMaskRange`   | `@Sensitive(strategy = StrategyMaskRange.class)`     | 指定范围脱敏   |\n\n## @Sensitive 定义\n\n```java\n@Inherited\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Sensitive {\n\n    /**\n     * 注解生效的条件\n     * @return 条件对应的实现类\n     */\n    Class\u003c? extends ICondition\u003e condition() default ConditionAlwaysTrue.class;\n\n    /**\n     * 执行的策略\n     * @return 策略对应的类型\n     */\n    Class\u003c? extends IStrategy\u003e strategy();\n\n}\n```\n\n## 与 @Sensitive 混合使用\n\n如果你将新增的注解 `@SensitiveStrategyChineseName` 与 `@Sensitive` 同时在一个字段上使用。\n\n为了简化逻辑，优先选择执行 `@Sensitive`，如果 `@Sensitive` 执行脱敏，\n那么 `@SensitiveStrategyChineseName` 将不会生效。\n\n如：\n\n```java\n/**\n * 测试字段\n * 1.当多种注解混合的时候，为了简化逻辑，优先选择 @Sensitive 注解。\n */\n@SensitiveStrategyChineseName\n@Sensitive(strategy = StrategyPassword.class)\nprivate String testField;\n```\n\n# 更多特性\n\n## 自定义脱敏策略生效的场景\n\n默认情况下，我们指定的场景都是生效的。\n\n但是你可能需要有些情况下不进行脱敏，比如有些用户密码为 123456，你觉得这种用户不脱敏也罢。\n\n- UserPasswordCondition.java\n\n```java\n@Sensitive(condition = ConditionFooPassword.class, strategy = StrategyPassword.class)\nprivate String password;\n``` \n\n其他保持不变，我们指定了一个 condition，实现如下：\n\n- ConditionFooPassword.java\n\n```java\npublic class ConditionFooPassword implements ICondition {\n    @Override\n    public boolean valid(IContext context) {\n        try {\n            Field field = context.getCurrentField();\n            final Object currentObj = context.getCurrentObject();\n            final String password = (String) field.get(currentObj);\n            return !password.equals(\"123456\");\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n}\n```\n\n也就是只有当密码不是 123456 时密码脱敏策略才会生效。\n\n## 属性为集合或者对象\n\n如果某个属性是单个集合或者对象，则需要使用注解 `@SensitiveEntry`。\n\n- 放在集合属性上，且属性为普通对象\n\n会遍历每一个属性，执行上面的脱敏策略。\n\n- 放在对象属性上\n\n会处理对象中各个字段上的脱敏注解信息。\n\n- 放在集合属性上，且属性为对象\n\n遍历每一个对象，处理对象中各个字段上的脱敏注解信息。\n\n### 放在集合属性上，且属性为普通对象\n\n- UserEntryBaseType.java\n\n作为演示，集合中为普通的字符串。\n\n```java\npublic class UserEntryBaseType {\n\n    @SensitiveEntry\n    @Sensitive(strategy = StrategyChineseName.class)\n    private List\u003cString\u003e chineseNameList;\n\n    @SensitiveEntry\n    @Sensitive(strategy = StrategyChineseName.class)\n    private String[] chineseNameArray;\n    \n    //Getter \u0026 Setter \u0026 toString()\n}\n```\n\n### 放在对象属性上\n\n例子如下：\n\n```java\npublic class UserEntryObject {\n\n    @SensitiveEntry\n    private User user;\n\n    @SensitiveEntry\n    private List\u003cUser\u003e userList;\n\n    @SensitiveEntry\n    private User[] userArray;\n    \n    //...\n}\n```\n\n# 自定义注解\n\n- v0.0.4 新增功能。允许功能自定义条件注解和策略注解。\n- v0.0.11 新增功能。允许功能自定义级联脱敏注解。\n\n## 案例1\n\n### 自定义密码脱敏策略\u0026自定义密码脱敏策略生效条件\n\n- 策略脱敏\n\n```java\n/**\n * 自定义密码脱敏策略\n * @author binbin.hou\n * date 2019/1/17\n * @since 0.0.4\n */\n@Inherited\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\n@SensitiveStrategy(CustomPasswordStrategy.class)\npublic @interface SensitiveCustomPasswordStrategy {\n}\n```\n\n- 脱敏生效条件\n\n```java\n/**\n * 自定义密码脱敏策略生效条件\n * @author binbin.hou\n * date 2019/1/17\n * @since 0.0.4\n */\n@Inherited\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\n@SensitiveCondition(ConditionFooPassword.class)\npublic @interface SensitiveCustomPasswordCondition{\n}\n```\n\n- TIPS\n\n`@SensitiveStrategy` 策略单独使用的时候，默认是生效的。\n\n如果有 `@SensitiveCondition` 注解，则只有当条件满足时，才会执行脱敏策略。\n\n`@SensitiveCondition` 只会对系统内置注解和自定义注解生效，因为 `@Sensitive` 有属于自己的策略生效条件。\n\n- 策略优先级\n\n`@Sensitive` 优先生效，然后是系统内置注解，最后是用户自定义注解。\n\n### 对应的实现\n\n两个元注解 `@SensitiveStrategy`、`@SensitiveCondition` 分别指定了对应的实现。\n\n- CustomPasswordStrategy.java\n\n```java\npublic class CustomPasswordStrategy implements IStrategy {\n\n    @Override\n    public Object des(Object original, IContext context) {\n        return \"**********************\";\n    }\n\n}\n```\n\n- ConditionFooPassword.java\n\n```java\n/**\n * 让这些 123456 的密码不进行脱敏\n * @author binbin.hou\n * date 2019/1/2\n * @since 0.0.1\n */\npublic class ConditionFooPassword implements ICondition {\n    @Override\n    public boolean valid(IContext context) {\n        try {\n            Field field = context.getCurrentField();\n            final Object currentObj = context.getCurrentObject();\n            final String name = (String) field.get(currentObj);\n            return !name.equals(\"123456\");\n        } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n```\n\n### 定义测试对象\n\n定义一个使用自定义注解的对象。\n \n```java\npublic class CustomPasswordModel {\n\n    @SensitiveCustomPasswordCondition\n    @SensitiveCustomPasswordStrategy\n    private String password;\n\n    @SensitiveCustomPasswordCondition\n    @SensitiveStrategyPassword\n    private String fooPassword;\n    \n    //其他方法\n}\n```\n\n### 测试\n\n```java\n/**\n * 自定义注解测试\n */\n@Test\npublic void customAnnotationTest() {\n    final String originalStr = \"CustomPasswordModel{password='hello', fooPassword='123456'}\";\n    final String sensitiveStr = \"CustomPasswordModel{password='**********************', fooPassword='123456'}\";\n    CustomPasswordModel model = buildCustomPasswordModel();\n    Assert.assertEquals(originalStr, model.toString());\n\n    CustomPasswordModel sensitive = SensitiveUtil.desCopy(model);\n    Assert.assertEquals(sensitiveStr, sensitive.toString());\n    Assert.assertEquals(originalStr, model.toString());\n}\n```\n\n构建对象的方法如下：\n\n```java\n/**\n * 构建自定义密码对象\n * @return 对象\n */\nprivate CustomPasswordModel buildCustomPasswordModel(){\n    CustomPasswordModel model = new CustomPasswordModel();\n    model.setPassword(\"hello\");\n    model.setFooPassword(\"123456\");\n    return model;\n}\n```\n\n## 案例2\n\n- v0.0.11 新增功能。允许功能自定义级联脱敏注解。\n\n### 自定义级联脱敏注解\n\n- 自定义级联脱敏注解\n\n可以根据自己的业务需要，在自定义的注解上使用 `@SensitiveEntry`。\n\n使用方式保持和 `@SensitiveEntry` 一样即可。\n\n```java\n/**\n * 级联脱敏注解,如果对象中属性为另外一个对象(集合)，则可以使用这个注解指定。\n * \u003cp\u003e\n * 1. 如果属性为 Iterable 的子类集合，则当做列表处理，遍历其中的对象\n * 2. 如果是普通对象，则处理对象中的脱敏信息\n * 3. 如果是普通字段/MAP，则不做处理\n * @since 0.0.11\n */\n@Inherited\n@Documented\n@Target(ElementType.FIELD)\n@Retention(RetentionPolicy.RUNTIME)\n@SensitiveEntry\npublic @interface SensitiveEntryCustom {\n}\n```\n\n### 定义测试对象\n\n定义一个使用自定义注解的对象。\n \n```java\npublic class CustomUserEntryObject {\n\n    @SensitiveEntryCustom\n    private User user;\n\n    @SensitiveEntryCustom\n    private List\u003cUser\u003e userList;\n\n    @SensitiveEntryCustom\n    private User[] userArray;\n\n    // 其他方法...\n}\n```\n\n# 生成脱敏后的 JSON\n\n## 说明\n\n为了避免生成中间脱敏对象，v0.0.6 之后直接支持生成脱敏后的 JSON。\n\n## 使用方法\n\n新增工具类方法，可以直接返回脱敏后的 JSON。\n\n生成的 JSON 是脱敏的，原对象属性值不受影响。\n\n```java\npublic static String desJson(Object object)\n```\n\n## 注解的使用方式\n\n和 `SensitiveUtil.desCopy()` 完全一致。\n\n## 使用示例代码\n\n所有的测试案例中，都添加了对应的 `desJson(Object)` 测试代码，可以参考。\n\n此处只展示最基本的使用。\n\n```java\nfinal String originalStr = \"SystemBuiltInAt{phone='18888888888', password='1234567', name='脱敏君', email='12345@qq.com', cardId='123456190001011234'}\";\nfinal String sensitiveJson = \"{\\\"cardId\\\":\\\"123456**********34\\\",\\\"email\\\":\\\"12******.com\\\",\\\"name\\\":\\\"脱**\\\",\\\"phone\\\":\\\"1888****888\\\"}\";\n\nSystemBuiltInAt systemBuiltInAt = DataPrepareTest.buildSystemBuiltInAt();\nAssert.assertEquals(sensitiveJson, SensitiveUtil.desJson(systemBuiltInAt));\nAssert.assertEquals(originalStr, systemBuiltInAt.toString());\n```\n\n## 注意\n\n本次 JSON 脱敏基于 [FastJSON](https://github.com/alibaba/fastjson)。\n\nFastJSON 在序列化本身存在一定限制。当对象中有集合，集合中还是对象时，结果不尽如人意。\n\n### 示例代码\n\n本测试案例可见测试代码。\n\n```java\nfinal 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'}}}\";\nfinal 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]\\\"}]}\";\nfinal 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]\\\"}]}\";\n\nUserCollection userCollection = DataPrepareTest.buildUserCollection();\n\nAssert.assertEquals(commonJson, JSON.toJSONString(userCollection));\nAssert.assertEquals(sensitiveJson, SensitiveUtil.desJson(userCollection));\nAssert.assertEquals(originalStr, userCollection.toString());\n```\n\n### 解决方案\n\n如果有这种需求，建议使用原来的 `desCopy(Object)`。\n\n# 脱敏引导类\n\n为了配置的灵活性，引入了引导类。\n\n## 配置属性\n\n引导类 SensitiveBs 的默认配置属性如下：\n\n```java\nSensitiveBs.newInstance()\n.deepCopy(FastJsonDeepCopy.getInstance())\n.hash(Hashes.empty())\n```\n\n## 核心 api 简介\n\n`SensitiveBs` 引导类的核心方法列表如下：\n\n| 序号 | 方法 | 参数 | 结果 | 说明 |\n|:---|:---|:---|:---|:---|\n| 1 | desCopy() | 目标对象 | 深度拷贝脱敏对象 | 适应性更强 |\n| 2 | desJson() | 目标对象 | 脱敏对象 json | 性能较好 |\n\n## 使用示例\n\n使用方式和工具类一致，示意如下：\n\n```java\nSensitiveBs.newInstance().desCopy(user);\n```\n\n## 配置哈希策略\n\n直接指定哈希策略即可，比如下面以 md5 作为值的哈希策略。\n\n```java\n// 指定哈希策略\nfinal SensitiveBs sensitiveBs = SensitiveBs.newInstance().hash(Hashes.md5());\n```\n\n效果如下：\n\n```\nfinal String originalStr = \"User{username='脱敏君', idCard='123456190001011234', password='1234567', email='12345@qq.com', phone='18888888888'}\";\nfinal String sensitiveStr = \"User{username='脱**|00871641C1724BB717DD01E7E5F7D98A', idCard='123456**********34|1421E4C0F5BF57D3CC557CFC3D667C4E', password='null', email='12******.com|6EAA6A25C8D832B63429C1BEF149109C', phone='1888****888|5425DE6EC14A0722EC09A6C2E72AAE18'}\";\nfinal String expectJson = \"{\\\"email\\\":\\\"12******.com|6EAA6A25C8D832B63429C1BEF149109C\\\",\\\"idCard\\\":\\\"123456**********34|1421E4C0F5BF57D3CC557CFC3D667C4E\\\",\\\"phone\\\":\\\"1888****888|5425DE6EC14A0722EC09A6C2E72AAE18\\\",\\\"username\\\":\\\"脱**|00871641C1724BB717DD01E7E5F7D98A\\\"}\";\n```\n\n## 配置深度拷贝实现\n\n默认的使用 FastJson 进行对象的深度拷贝，等价于：\n\n```java\nSensitiveBs.newInstance()\n                .deepCopy(FastJsonDeepCopy.getInstance())\n                .desJson(user);\n```\n\n参见 [SensitiveBsTest.java](https://github.com/houbb/sensitive/blob/master/sensitive-test/src/test/java/com/github/houbb/sensitive/test/bs/SensitiveBsTest.java)\n\ndeepCopy 用于指定深度复制的具体实现，支持用户自定义。\n\n# 深度复制（DeepCopy）\n\n## 说明\n\n深度复制可以保证我们日志输出对象脱敏，同时不影响正常业务代码的使用。\n\n可以实现深度复制的方式有很多种，默认基于 [fastjson](https://github.com/alibaba/fastjson) 实现的。\n\n为保证后续良性发展，v0.0.13 版本之后将深度复制接口抽离为单独的项目：\n\n\u003e [deep-copy](https://github.com/houbb/deep-copy)\n\n## 内置策略\n\n目前支持 6 种基于序列化实现的深度复制，便于用户替换使用。\n\n每一种都可以单独使用，保证依赖更加轻量。\n\n## 自定义\n\n为满足不同场景的需求，深度复制策略支持用户自定义。\n\n\u003e [自定义深度复制](https://github.com/houbb/deep-copy#%E8%87%AA%E5%AE%9A%E4%B9%89)\n\n# log4j2 插件统一脱敏\n\n## 说明\n\n上面的方法非常适用于新的项目，按照响应的规范进行推广。\n\n但是很多金融公司都有很多历史遗留项目，或者使用不规范，比如使用 map 等，导致上面的方法在脱敏技改时需要耗费大量的时间，而且回溯成本很高。\n\n有没有什么方法，可以直接在日志层统一处理呢？\n\n## log4j2 Rewrite\n\n我们可以基于 log4j2 RewritePolicy 统一使用脱敏策略。\n\n说明：如果使用 slf4j 接口，实现为 log4j2 时也是支持的。\n\n## 使用入门\n\n### maven 引入\n\n引入核心脱敏包。\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.houbb\u003c/groupId\u003e\n    \u003cartifactId\u003esensitive-log4j2\u003c/artifactId\u003e\n    \u003cversion\u003e1.7.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n其他的一般项目中也有，如 log4j2 包：\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.logging.log4j\u003c/groupId\u003e\n    \u003cartifactId\u003elog4j-api\u003c/artifactId\u003e\n    \u003cversion\u003e${log4j2.version}\u003c/version\u003e\n\u003c/dependency\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.apache.logging.log4j\u003c/groupId\u003e\n    \u003cartifactId\u003elog4j-core\u003c/artifactId\u003e\n    \u003cversion\u003e${log4j2.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### log4j2.xml 配置\n\n例子如下:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cConfiguration status=\"WARN\" packages = \"com.github.houbb.sensitive.log4j2.layout\"\u003e\n\n    \u003cProperties\u003e\n        \u003cProperty name=\"DEFAULT_PATTERN\"\u003e%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\u003c/Property\u003e\n        \u003cProperty name=\"DEFAULT_CHARSET\"\u003eUTF-8\u003c/Property\u003e\n    \u003c/Properties\u003e\n\n    \u003cAppenders\u003e\n        \u003cConsole name=\"Console\" target=\"SYSTEM_OUT\"\u003e\n            \u003cSensitivePatternLayout/\u003e\n        \u003c/Console\u003e\n    \u003c/Appenders\u003e\n\n    \u003cLoggers\u003e\n        \u003cRoot level=\"DEBUG\"\u003e\n            \u003cAppenderRef ref=\"Console\"/\u003e\n        \u003c/Root\u003e\n    \u003c/Loggers\u003e\n\n\u003c/Configuration\u003e\n```\n\n几个步骤：\n\n1. 指定 package 为 `packages = \"com.github.houbb.sensitive.log4j2.layout\"`\n\n2. 按照 log4j2 layout 规范，指定 Layout 策略为 `SensitivePatternLayout`\n\n### 测试\n\n正常的日志打印：\n\n```java\nprivate static final String TEST_LOG = \"mobile:13088887777; bankCard:6217004470007335024, email:mahuateng@qq.com, amount:123.00, \" +\n        \"IdNo:340110199801016666, name1:李明, name2:李晓明, name3:李泽明天, name4:山东小栗旬\" +\n        \", birthday:20220517, GPS:120.882222, IPV4:127.0.0.1, address:中国上海市徐汇区888号;\";\n\nlogger.info(TEST_LOG);\n```\n\n自动脱敏效果如下：\n\n```\n01:37:28.010 [main] INFO  com.github.houbb.sensitive.test.log4j2.Log4j2AndSlf4jLayoutTest - mobile:130****7777|9FC4D36D63D2B6DC5AE1297544FBC5A2; bankCard:6217***********5024|444F49289B30944AB8C6C856AEA21180, email:mahu*****@qq.com|897915594C94D981BA86C9E83ADD449C, amount:123.00, IdNo:340110199801016666, name1:李明, name2:李晓明, name3:李泽明天, name4:山东小栗旬, birthday:20220517, GPS:120.882222, IPV4:127.0.0.1, address:中国上海市徐******|821A601949B1BD18DCBAAE27F2E27147;\n```\n\nps: 这里是为了演示各种效果，实际默认对应为 1,2,3,4,9 这几种策略。 \n\n## log4j2 配置定制化\n\n为了满足各种用户的场景，在 V1.6.0 引入了 SensitivePatternLayout 策略的可配置化。\n\n用户可以在应用 resources 下通过 `chars-scan-config.properties` 配置文件指定。\n\n### 默认配置\n\nlog4j2 配置中，`SensitivePatternLayout` 配置默认为：\n\n```properties\nchars.scan.prefix=:：,，'\"‘“=| +()（）\nchars.scan.scanList=1,2,3,4,9\nchars.scan.replaceList=1,2,3,4,9\nchars.scan.defaultReplace=12\nchars.scan.replaceHash=md5\nchars.scan.whiteList=\"\"\n```\n\n### 属性说明\n\nSensitivePatternLayout 策略的属性说明。\n\n| 属性 | 说明          | 默认值                | 备注                                       |\n|:---|:------------|:-------------------|:-----------------------------------------|\n|  prefix  | 需要脱敏信息的匹配前缀 | `:：,，'\"‘“= +()（）` 和英文竖线 | 降低误判率                                    |\n|  replaceHash  | 哈希策略模式      | `md5`              | 支持 md5/none 两种模式                         |\n|  scanList  | 敏感扫描策略列表    | `1,2,3,4`          | 1~10 内置的10种敏感信息扫描策略，多个用逗号隔开              |\n|  replaceList  | 敏感替换策略列表    | `1,2,3,4`          | 1~10 内置的10种敏感信息替换策略，多个用逗号隔开              |\n|  defaultReplace  | 敏感替换默认策略    | `12`               | 1~13 内置的13种敏感信息替换策略，指定一个。当列表没有匹配时，默认使用这个 |\n|  whiteList  | 白名单         | ``               | 希望跳过处理的白名单信息                             |\n\n其中 1-13 的内置策略说明如下：\n\n| 策略标识 | 说明                     |\n|:-----|:-----------------------|\n| 1    | 手机号                    |\n| 2    | 身份证                    |\n| 3    | 银行卡                    |\n| 4    | 邮箱                     |\n| 5    | 中国人名                   |\n| 6    | 出生日期                   |\n| 7    | GPS                    |\n| 8    | IPV4                   |\n| 9    | 地址                     |\n| 10   | 护照                     |\n| 11   | 匹配任意不掩盖                |\n| 12   | 匹配任意半掩盖                |\n| 13   | 匹配任意全掩盖                |\n| m1   | 数字类合并操作(m1:1\u00262\u00263) 性能更好 |\n| m3   | 拓展类合并操作(m3:4\u00265\u00269) 性能更好 |\n\n### 不足之处\n\n这里的策略自定义和 log4j2 的插件化比起来，确实算不上强大，但是可以满足 99% 的脱敏场景。\n\n后续有时间考虑类似 log4j2 的 plugins 思想，实现更加灵活的自定义策略。\n\n# logback 脱敏插件\n\n## 说明\n\n为了便于用户使用，v1.6.0 开始支持 logback 插件模式。\n\n## 使用入门\n\n### maven 引入\n\n引入核心脱敏包。\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.github.houbb\u003c/groupId\u003e\n    \u003cartifactId\u003esensitive-logback\u003c/artifactId\u003e\n    \u003cversion\u003e1.7.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n引入 logback 依赖包\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ech.qos.logback\u003c/groupId\u003e\n    \u003cartifactId\u003elogback-classic\u003c/artifactId\u003e\n    \u003cversion\u003e${logback.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### 指定 logback.xml 配置\n\n```xml\n\u003cconfiguration\u003e\n    \u003c!-- 基于 converter --\u003e\n    \u003cconversionRule conversionWord=\"sensitive\" converterClass=\"com.github.houbb.sensitive.logback.converter.SensitiveLogbackConverter\" /\u003e\n    \u003c!-- 使用 converter --\u003e\n    \u003cappender name=\"STDOUTConverter\" class=\"ch.qos.logback.core.ConsoleAppender\"\u003e\n        \u003cencoder\u003e\n            \u003cpattern\u003e%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %sensitive%n\u003c/pattern\u003e\n        \u003c/encoder\u003e\n    \u003c/appender\u003e\n\n    \u003c!-- 使用 layout --\u003e\n    \u003cappender name=\"STDOUTLayout\" class=\"ch.qos.logback.core.ConsoleAppender\"\u003e\n        \u003clayout class=\"com.github.houbb.sensitive.logback.layout.SensitiveLogbackLayout\"\u003e\n            \u003cpattern\u003e%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\u003c/pattern\u003e\n        \u003c/layout\u003e\n    \u003c/appender\u003e\n\n    \u003c!-- 设置根日志级别为DEBUG，并将日志输出到控制台 --\u003e\n    \u003croot level=\"DEBUG\"\u003e\n        \u003cappender-ref ref=\"STDOUTConverter\"/\u003e\n        \u003cappender-ref ref=\"STDOUTLayout\"/\u003e\n    \u003c/root\u003e\n\u003c/configuration\u003e\n```\n\n这里共计支持 Converter 和 Layout 两种模式，任选一个即可。\n\n建议使用 SensitiveLogbackConverter，脱敏日志内容。\n\n## 日志效果\n\n脱密效果和 log4j2 类似，如下：\n\n```\n01:42:32.579 [main] INFO  c.g.h.sensitive.test2.LogbackMain - mobile:130****7777|9FC4D36D63D2B6DC5AE1297544FBC5A2; bankCard:6217***********5024|444F49289B30944AB8C6C856AEA21180, email:mahu*****@qq.com|897915594C94D981BA86C9E83ADD449C, amount:123.00, \" + \"IdNo:340110199801016666, name1:李明, name2:李晓明, name3:李泽明天, name4:山东小栗旬\" + \", birthday:20220517, GPS:120.882222, IPV4:127.0.0.1, address:中国上海市徐******|821A601949B1BD18DCBAAE27F2E27147;\n```\n\n## 配置属性\n\n同 log4j2，此处不再赘述。\n\n# 性能耗时\n\n## 注解\n\n100W 次耗时统计\n\n| 方法      | 耗时(ms)  | 说明                         |\n|:--------|:--------|:---------------------------|\n| 原始工具类方法 | 122     | 性能最好，但是最麻烦。拓展性最差           |\n| JSON.toJSONString(user) | 304     | 性能较好，拓展性不错。缺点是强依赖 fastjson |\n| SensitiveUtil.desJson(user) | 1541    | 性能较差，拓展性最好，比较灵活            |\n\n# ROAD-MAP\n\n- [x] 添加统一的工具类方法，便于开发单独使用\n\n喜欢重载 toString()，或特殊的场景\n\n- [x] 考虑添加针对 MAP 的脱敏支持\n\n- [x] 针对身份证的默认脱敏策略\n\n- [x] log4j2 等日志组件的脱敏策略\n\n提升可拓展性\n\n- [x] log4j2 layout 对应的脱敏策略\n\n- [x] 优化代码实现，直接继承自 patternLayout\n\n- [x] log4j2 脱敏配置添加指定配置文件，而不是放在 pattern 中\n\n- [ ] 日志插件脱敏的 benchmark 性能报告\n","funding_links":[],"categories":["Java","日志库"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fsensitive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoubb%2Fsensitive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoubb%2Fsensitive/lists"}