开篇引入
在 Java 项目开发中,日志是我们排查问题、监控系统运行状态的得力助手。但你是否想过,在记录日志时,可能会不小心把敏感信息也一并记录下来,从而带来安全风险?比如用户的身份证号、银行卡号、密码等,一旦这些信息在日志中泄露,后果不堪设想。
为了解决这个问题,今天给大家介绍一款超好用的 Java 日志脱敏框架 ——Sensitive。它能让我们在记录日志时,轻松对敏感信息进行脱敏处理,既保证了日志的可读性,又保障了数据的安全性。接下来,就让我们一起深入了解 Sensitive 框架的强大功能吧。
什么是 Sensitive 框架
框架简介
Sensitive 是一个基于 Java 注解的日志脱敏工具框架 ,由 houbb 开发,它就像是一个细心的信息守护者,默默地为 Java 应用的日志处理加上了一层安全锁。通过简单易用的注解,它能够在日志记录的过程中,精准地识别出敏感信息,并按照预设的规则进行脱敏处理,从而有效地保障用户隐私和数据安全,为我们的应用程序保驾护航。
核心功能
1、 基于注解的日志脱敏:这是 Sensitive 框架的一大特色。只需要在需要脱敏的字段上添加自定义注解,就能轻松为该字段指定脱敏策略。比如,在用户实体类中,对密码字段添加@Sensitive(strategy = StrategyPassword.class)注解,这样在记录包含该用户信息的日志时,密码字段就会自动按照密码脱敏策略进行处理,变得不可见。
@Getter
@Setter
public class User {
@Sensitive(strategy = StrategyChineseName.class)
private String username;
@Sensitive(strategy = StrategyCardId.class)
private String idCard;
@Sensitive(strategy = StrategyPassword.class)
private String password;
}
2、 内置多种脱敏策略:Sensitive 贴心地内置了常见的十几种脱敏策略,涵盖了我们日常开发中几乎所有可能遇到的敏感信息类型。像手机号,会将中间 4 位替换为****,比如13800138000脱敏后变成138****8000;邮箱则会将用户名部分保留前几位,后面用*代替,例如example@qq.com脱敏后为exa***``@qq.com ;密码通常直接设置为null或者用特定符号代替,以确保其安全性。这些内置策略经过精心设计,能够满足大多数项目的基本需求,大大减少了我们手动编写脱敏逻辑的工作量。
3、 支持自定义策略:尽管内置策略已经很丰富,但在实际开发中,我们可能还会遇到一些特殊的脱敏需求。别担心,Sensitive 允许开发者根据实际情况自定义脱敏策略。比如,对于某些特定格式的订单号,我们可以编写自己的脱敏逻辑,实现个性化的脱敏处理。只需要实现IStrategy接口,并重写其中的des方法,就可以定义自己的脱敏策略啦。这样一来,无论需求多么独特,Sensitive 都能灵活应对,完美满足我们的个性化需求。
public class CustomStrategy implements IStrategy {
@Override
public Object des(Object original, IContext context) {
// 自定义脱敏逻辑
String value = (String) original;
return value.substring(0, 3) + "***" + value.substring(value.length() - 3);
}
}
4、 支持深拷贝:在处理复杂对象时,Sensitive 的深拷贝功能就派上用场了。它能够对对象进行深拷贝,同时保留脱敏效果。这意味着我们可以在不影响原始对象的情况下,使用脱敏后的对象进行日志记录等操作。即使对象中包含嵌套的对象或集合,Sensitive 也能准确无误地对其进行脱敏和深拷贝,确保数据的完整性和安全性。比如,一个包含用户信息的订单对象,用户信息中的敏感字段经过脱敏后,整个订单对象被深拷贝用于日志记录,而原始订单对象及其用户信息保持不变,非常方便实用。
5、 支持 FastJSON:在当今的 Web 开发中,JSON 数据格式被广泛使用。Sensitive 与 FastJSON 的完美结合,让我们可以基于 FastJSON 直接生成脱敏后的 JSON。这在前后端数据交互频繁的场景下,大大提高了数据处理的效率和安全性。当我们需要将包含敏感信息的对象返回给前端时,只需要使用 Sensitive 的相关方法,就能快速生成脱敏后的 JSON 数据,确保敏感信息不会泄露到前端。例如,在一个用户信息查询接口中,将用户对象转换为脱敏后的 JSON 返回给前端,既保证了前端展示的需求,又保障了用户数据的安全。
6、 支持自定义哈希策略:在排查日志问题时,有时候我们需要一种既能隐藏敏感信息,又能方便定位问题的方法。Sensitive 支持自定义哈希策略,就是为了解决这个问题而生的。通过自定义哈希策略,我们可以对敏感信息进行哈希处理,生成一个唯一的标识。这样在日志中,敏感信息以哈希值的形式出现,既能保护信息安全,又能在需要时通过哈希值快速定位到相关的日志记录,方便我们进行问题排查和分析,为我们的开发和运维工作提供了极大的便利。
快速上手 Sensitive 框架
环境准备
在开始使用 Sensitive 框架之前,首先要确保我们的开发环境满足要求。需要 JDK 1.8 及以上版本,因为 Sensitive 框架充分利用了 Java 8 的一些特性,能够更好地发挥其功能 。同时,项目构建工具使用 Maven 3.x,Maven 强大的依赖管理功能,能帮助我们轻松引入 Sensitive 框架及其相关依赖。
引入依赖
在pom.xml文件中添加 Sensitive 框架的依赖,就像给房子搭建基础一样,为项目引入关键的功能模块:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-core</artifactId>
<version>1.7.0</version>
</dependency>
添加好依赖后,点击 Maven 的Reimport按钮,Maven 就会自动从中央仓库下载Sensitive-core及其依赖的相关库,确保项目能够顺利使用 Sensitive 框架的各项功能 。
定义需要脱敏的对象
接下来,我们以一个用户实体类User为例,来看看如何使用注解标记敏感字段。假设我们有一个用户类,包含用户名、身份证号、密码、邮箱和手机号等信息,其中这些信息都属于敏感信息,需要进行脱敏处理。
import com.github.houbb.sensitive.annotation.Sensitive;
import com.github.houbb.sensitive.core.api.strategy.StrategyCardId;
import com.github.houbb.sensitive.core.api.strategy.StrategyChineseName;
import com.github.houbb.sensitive.core.api.strategy.StrategyEmail;
import com.github.houbb.sensitive.core.api.strategy.StrategyPassword;
import com.github.houbb.sensitive.core.api.strategy.StrategyPhone;
import lombok.Data;
@Data
public class User {
// 用户名
@Sensitive(strategy = StrategyChineseName.class)
private String username;
// 身份证号
@Sensitive(strategy = StrategyCardId.class)
private String idCard;
// 密码
@Sensitive(strategy = StrategyPassword.class)
private String password;
// 邮箱
@Sensitive(strategy = StrategyEmail.class)
private String email;
// 手机号
@Sensitive(strategy = StrategyPhone.class)
private String phone;
}
在这个User类中,我们在每个敏感字段上都添加了@Sensitive注解,并通过strategy属性指定了对应的脱敏策略 。比如,username字段使用StrategyChineseName.class策略,它会将中文姓名的中间部分替换为*;idCard字段使用StrategyCardId.class策略,会把身份证号的出生日期部分替换为*,只保留前后几位关键信息 ,这样既保证了一定的可识别性,又保护了敏感信息。
脱敏操作示例
一切准备就绪后,我们来编写一段测试代码,展示如何调用 Sensitive 工具类对对象进行脱敏,并输出脱敏前后的结果,直观感受 Sensitive 框架的强大功能。
import com.github.houbb.sensitive.core.api.SensitiveUtil;
public class SensitiveTest {
public static void main(String[] args) {
// 创建一个User对象并设置属性值
User user = new User();
user.setUsername("程序员波特");
user.setIdCard("110105199003071234");
user.setPassword("123456");
user.setEmail("pottercoding@linux.do");
user.setPhone("13800138000");
// 输出脱敏前的User对象
System.out.println("脱敏前: " + user);
// 调用SensitiveUtil的desCopy方法对User对象进行脱敏,得到脱敏后的对象
User sensitiveUser = SensitiveUtil.desCopy(user);
// 输出脱敏后的User对象
System.out.println("脱敏后: " + sensitiveUser);
}
}
运行这段代码,我们可以在控制台看到如下输出:

从输出结果可以清晰地看到,脱敏后的对象中,各个敏感字段都已经按照我们设定的策略进行了脱敏处理 ,用户名中间部分被替换为*,身份证号的出生日期部分被隐藏,密码直接显示为null,邮箱和手机号的敏感部分也都被脱敏,有效保护了用户的隐私信息。
高级应用与自定义
自定义脱敏策略
虽然 Sensitive 框架内置了丰富的脱敏策略,但在实际项目中,我们可能会遇到一些特殊的脱敏需求,这时就需要自定义脱敏策略啦。比如,我们有一个特殊格式的订单号,前 3 位是固定的业务标识,中间 4 位是日期,后 5 位是流水号,我们希望只保留业务标识和流水号,中间的日期部分用****代替 。下面我们来看一下具体的实现步骤:
1、 实现IStrategy接口:首先,创建一个类实现IStrategy接口,并重写des方法,在这个方法中编写我们的自定义脱敏逻辑。
import com.github.houbb.sensitive.api.IContext;
import com.github.houbb.sensitive.api.IStrategy;
public class CustomOrderNumberStrategy implements IStrategy {
@Override
public Object des(Object original, IContext context) {
String orderNumber = (String) original;
if (orderNumber.length() != 12) {
return orderNumber;
}
return orderNumber.substring(0, 3) + "****" + orderNumber.substring(7);
}
}
2、使用自定义策略:在需要脱敏的字段上使用@Sensitive注解,并指定strategy为我们自定义的策略类。
@Data
public class Order {
@Sensitive(strategy = CustomOrderNumberStrategy.class)
private String orderNumber;
}
3、测试自定义策略:编写测试代码,验证我们的自定义策略是否生效。
import com.github.houbb.sensitive.core.api.SensitiveUtil;
public class CustomStrategyTest {
public static void main(String[] args) {
Order order = new Order();
order.setOrderNumber("ABC202501011");
Order sensitiveOrder = SensitiveUtil.desCopy(order);
System.out.println("脱敏前订单号: " + order.getOrderNumber());
System.out.println("脱敏后订单号: " + sensitiveOrder.getOrderNumber());
}
}
运行测试代码,输出结果如下:

从结果可以看到,我们的自定义脱敏策略成功生效,订单号按照我们的预期进行了脱敏处理。
自定义策略生效条件
在某些业务场景下,我们可能希望脱敏策略只有在满足特定条件时才生效。比如,对于用户的身份证号,只有在用户不是管理员时才进行脱敏。下面我们通过一个示例来看看如何设置脱敏策略的生效条件:
1、 实现ICondition接口:创建一个类实现ICondition接口,并重写valid方法,在这个方法中定义我们的生效条件。
import com.github.houbb.sensitive.api.strategy.IContext;
import com.github.houbb.sensitive.api.strategy.ICondition;
public class NonAdminCondition implements ICondition {
@Override
public boolean valid(IContext context) {
try {
// 假设User类中有一个isAdmin方法判断是否是管理员
Object currentObj = context.getCurrentObject();
if (currentObj instanceof User) {
User user = (User) currentObj;
return!user.isAdmin();
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
2、使用生效条件:在@Sensitive注解中,通过condition属性指定我们实现的条件类。
public class User {
@Sensitive(strategy = StrategyCardId.class, condition = NonAdminCondition.class)
private String idCard;
private boolean isAdmin;
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public boolean isAdmin() {
return isAdmin;
}
public void setAdmin(boolean admin) {
isAdmin = admin;
}
}
3、测试生效条件:编写测试代码,分别测试管理员和普通用户的身份证号脱敏情况 。
public class ConditionTest {
public static void main(String[] args) {
User adminUser = new User();
adminUser.setIdCard("110105199003071234");
adminUser.setAdmin(true);
User sensitiveAdminUser = SensitiveUtil.desCopy(adminUser);
System.out.println("管理员脱敏前身份证号: " + adminUser.getIdCard());
System.out.println("管理员脱敏后身份证号: " + sensitiveAdminUser.getIdCard());
User normalUser = new User();
normalUser.setIdCard("110105199003071234");
normalUser.setAdmin(false);
User sensitiveNormalUser = SensitiveUtil.desCopy(normalUser);
System.out.println("普通用户脱敏前身份证号: " + normalUser.getIdCard());
System.out.println("普通用户脱敏后身份证号: " + sensitiveNormalUser.getIdCard());
}
}
运行测试代码,输出结果如下:

从结果可以看出,管理员的身份证号没有脱敏,而普通用户的身份证号按照策略进行了脱敏,说明我们设置的生效条件成功起作用啦.
处理复杂对象结构
在实际开发中,我们经常会遇到包含集合或嵌套对象的复杂对象结构。Sensitive 框架同样能够很好地处理这种情况 。
假设我们有一个Order类,其中包含一个User对象和一个商品列表List<Product>,User对象和Product对象中都有需要脱敏的字段 。
import com.github.houbb.sensitive.annotation.Sensitive;
import com.github.houbb.sensitive.annotation.SensitiveEntry;
import com.github.houbb.sensitive.core.api.strategy.StrategyCardId;
import com.github.houbb.sensitive.core.api.strategy.StrategyChineseName;
import com.github.houbb.sensitive.core.api.strategy.StrategyPassword;
import java.util.List;
import lombok.Data;
@Data
public class Order {
@SensitiveEntry
private User user;
@SensitiveEntry
private List<Product> products;
}
@Data
class User {
@Sensitive(strategy = StrategyChineseName.class)
private String username;
@Sensitive(strategy = StrategyCardId.class)
private String idCard;
@Sensitive(strategy = StrategyPassword.class)
private String password;
}
@Data
class Product {
@Sensitive(strategy = StrategyChineseName.class)
private String productName;
private double price;
}
在上述代码中,我们在Order类的user和products属性上添加了@SensitiveEntry注解 。这个注解的作用是告诉 Sensitive 框架,需要对该属性所指向的对象或集合中的元素进行脱敏处理 。当我们对Order对象进行脱敏操作时,Sensitive 框架会自动递归处理User对象和Product对象中的敏感字段 。
下面是测试代码:
import com.github.houbb.sensitive.core.util.SensitiveUtil;
import java.util.Arrays;
public class ComplexObjectTest {
public static void main(String[] args) {
Order order = new Order();
User user = new User();
user.setUsername("程序员波特");
user.setIdCard("110105199003071234");
user.setPassword("123456");
order.setUser(user);
Product product1 = new Product();
product1.setProductName("iPhone 15");
product1.setPrice(7999.0);
Product product2 = new Product();
product2.setProductName("MacBook Pro");
product2.setPrice(14999.0);
order.setProducts(Arrays.asList(product1, product2));
Order sensitiveOrder = SensitiveUtil.desCopy(order);
System.out.println("脱敏前订单信息: " + order);
System.out.println("脱敏后订单信息: " + sensitiveOrder);
}
}
运行测试代码,输出结果如下:

从结果可以清晰地看到,无论是嵌套的User对象,还是集合中的Product对象,其敏感字段都成功进行了脱敏处理 ,Sensitive 框架在处理复杂对象结构时的强大功能可见一斑。
与日志框架集成
在实际项目中,我们通常会使用各种日志框架来记录系统运行时的信息。Sensitive 框架可以与常见的日志框架如 Logback 和 Log4j2 完美集成,实现日志的自动脱敏,让我们在享受日志框架强大功能的同时,也能确保敏感信息的安全。
与 Logback 集成
1、 添加依赖:首先,在pom.xml文件中添加 Sensitive 框架对 Logback 支持的依赖:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-logback</artifactId>
<version>1.7.0</version>
</dependency>
2、配置 Logback:在logback.xml或logback-spring.xml配置文件中,添加 Sensitive 的Converter。假设我们要对日志中的手机号和身份证号进行脱敏:
<configuration>
<!-- 定义Sensitive的Converter,用于脱敏处理 -->
<conversionRule conversionWord="sensitive" converterClass="com.github.houbb.sensitive.logback.converter.SensitiveConverter"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 使用Sensitive的Converter进行日志格式化,实现脱敏 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %sensitive(%msg)%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
在上述配置中,<conversionRule>标签定义了一个名为sensitive的Converter,它会在日志输出时对消息进行脱敏处理。<pattern>标签中使用%sensitive(%msg),表示对日志消息%msg进行脱敏后再输出。 这样,当我们在项目中使用 Logback 记录包含敏感信息的日志时,Sensitive 框架会自动根据我们之前定义的脱敏策略对敏感信息进行脱敏,然后再输出到日志中 。
与 Log4j2 集成
1、 添加依赖:在pom.xml文件中添加 Sensitive 框架对 Log4j2 支持的依赖:
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>sensitive-log4j2</artifactId>
<version>1.7.0</version>
</dependency>
2、配置 Log4j2:在log4j2.xml配置文件中,配置 Sensitive 的Filter。以对手机号和身份证号脱敏为例:
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
<!-- 添加Sensitive的Filter,对日志事件进行脱敏过滤 -->
<Filters>
<SensitiveFilter>
<Patterns>
<Pattern>(\d{3})\d{4}(\d{4})</Pattern> <!-- 匹配手机号正则 -->
<Pattern>(\d{4})\d{10}(\w{4})</Pattern> <!-- 匹配身份证号正则 -->
</Patterns>
<Replacement>$1****$2</Replacement> <!-- 脱敏替换格式 -->
</SensitiveFilter>
</Filters>
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
在这个配置中,<SensitiveFilter>标签定义了一个脱敏过滤器,<Patterns>标签中定义了要匹配的敏感信息正则表达式,<Replacement>标签指定了脱敏后的替换格式。当 Log4j2 记录日志时,会先经过这个过滤器,对匹配到的敏感信息进行脱敏处理后再输出,确保日志中的敏感信息得到有效保护。
性能优化建议
在使用 Sensitive 框架时,虽然它已经具备了不错的性能表现,但我们仍然可以通过一些方法进一步优化,以满足高并发、大数据量的业务场景需求 。
1、 避免不必要的对象创建:在频繁调用脱敏方法的场景下,尽量重用已经创建好的对象,避免每次都创建新的对象。比如,对于一些固定的脱敏策略对象,可以将其定义为静态常量,这样在多次调用时,就不需要重复创建,减少内存开销和对象创建时间 。以自定义脱敏策略为例,如果有一个经常使用的自定义策略类CustomStrategy,可以这样定义:
public class SensitiveUtils {
private static final IStrategy CUSTOM_STRATEGY = new CustomStrategy();
public static Object desensitize(Object original) {
return CUSTOM_STRATEGY.des(original, null);
}
}
2、批量处理数据:如果需要对大量数据进行脱敏,不要逐一对每个数据进行脱敏操作,而是将数据批量处理。比如,对于一个包含多个用户信息的列表,可以一次性将整个列表传递给SensitiveUtil.desCopyCollection方法进行批量脱敏,而不是循环遍历列表,对每个用户对象单独调用脱敏方法,这样可以减少方法调用的开销,提高处理效率。
List<User> userList = new ArrayList<>();
// 添加多个用户对象到userList
List<User> sensitiveUserList = SensitiveUtil.desCopyCollection(userList);
3、合理使用缓存:对于一些频繁使用且脱敏结果不会改变的数据,可以考虑使用缓存来存储脱敏后的结果。这样在下次需要使用时,直接从缓存中获取,避免重复脱敏操作。例如,对于一些系统配置信息中的敏感字段,其脱敏结果在系统运行期间基本不会改变,可以将脱敏后的结果缓存起来,在需要打印相关日志时,直接从缓存中获取,提高日志记录的速度。可以使用 Guava Cache 来实现简单的缓存功能:
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class SensitiveCache {
private static final Cache<String, Object> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();
public static Object getFromCache(String key) {
return cache.getIfPresent(key);
}
public static void putToCache(String key, Object value) {
cache.put(key, value);
}
}
在使用时:
String key = "systemConfigSensitiveData";
Object desensitizedData = SensitiveCache.getFromCache(key);
if (desensitizedData == null) {
// 进行脱敏操作
desensitizedData = SensitiveUtil.desCopy(originalData);
SensitiveCache.putToCache(key, desensitizedData);
}
本文小结
Sensitive 框架作为 Java 日志脱敏的得力助手,凭借其基于注解的便捷操作、丰富的内置脱敏策略、灵活的自定义能力、出色的性能表现以及与常见日志框架的良好集成性,为我们在保障日志安全方面提供了全方位的支持。无论是处理简单的用户信息,还是复杂的业务对象,它都能轻松应对,有效地保护敏感信息不被泄露。
如果你还在为日志中的敏感信息处理而烦恼,不妨试试 Sensitive 框架,相信它会给你带来意想不到的便利和高效。在使用过程中,如果你有任何问题或者建议,欢迎在评论区留言,让我们一起交流探讨,共同提升项目的安全性和可靠性。期待大家都能在自己的项目中优雅地打印脱敏日志,为数据安全保驾护航。
974

被折叠的 条评论
为什么被折叠?



