SpringBoot+logback 日志打印脱敏,正常获取对象不受影响
添加依赖#
注意:springboot版本2.7.0
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency>
自定义枚举值#
/** * 脱敏枚举值 */ public enum SensitiveType { DEFAULT, // 默认规则(部分隐藏) PHONE, // 手机号 ID_CARD, // 身份证号 EMAIL, // 邮箱 ADDRESS, //地址 USER_NAME, //用户名 NUMBER; //数字,直接全脱敏 }
自定义注解#
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Sensitive { SensitiveType type() default SensitiveType.DEFAULT; }
重写ClassicConverter#
import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import com.ybchen.log.Sensitive; import com.ybchen.log.SensitiveType; import org.slf4j.helpers.MessageFormatter; import java.lang.reflect.Field; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * @description: 敏感日志转换器 * @author: 陈彦斌 * @create: 2024-12-09 18:55 */ public class SensitiveLogConverter extends ClassicConverter { @Override public String convert(ILoggingEvent event) { Object[] args = event.getArgumentArray(); if (args == null || args.length == 0) { return event.getFormattedMessage(); } for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg != null) { args[i] = desensitize(arg); } } return MessageFormatter.arrayFormat(event.getMessage(), args).getMessage(); } private Object desensitize(Object obj) { if (obj == null) { return null; } try { Class<?> clazz = obj.getClass(); // 如果对象是集合类型 if (obj instanceof List) { List<?> list = (List<?>) obj; List<Object> newList = new ArrayList<>(); for (Object item : list) { newList.add(desensitize(item)); // 递归处理列表中的每个对象 } return newList; } // 如果对象是普通类型,直接返回 if (isPrimitiveOrWrapper(clazz) || clazz == String.class) { return obj; } // 创建新实例 Object newObj = clazz.getDeclaredConstructor().newInstance(); // 遍历字段 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); Object fieldValue = field.get(obj); // 获取原字段值 if (fieldValue == null){ continue; } if (field.isAnnotationPresent(Sensitive.class)) { // 处理标注为 @Sensitive 的字段 if (fieldValue instanceof String) { Sensitive sensitive = field.getAnnotation(Sensitive.class); SensitiveType type = sensitive.type(); field.set(newObj, applyDesensitization((String) fieldValue, type)); }else if ( fieldValue instanceof Integer || fieldValue instanceof Long || fieldValue instanceof BigDecimal || fieldValue instanceof Byte || fieldValue instanceof Short || fieldValue instanceof Float || fieldValue instanceof Double ){ //数字直接脱敏 field.set(newObj, null); }else if (fieldValue instanceof List) { // 如果字段是 List 类型,递归处理其内部元素 List<?> list = (List<?>) fieldValue; List<Object> newList = new ArrayList<>(); for (Object item : list) { newList.add(desensitize(item)); } field.set(newObj, newList); } else { // 对其他非字符串类型的字段,递归脱敏 field.set(newObj, desensitize(fieldValue)); } } else { // 未标注 @Sensitive 的字段直接复制 field.set(newObj, fieldValue); } } return newObj; // 返回脱敏后的新对象 } catch (Exception e) { e.printStackTrace(); return obj; // 异常情况下返回原对象 } } private String applyDesensitization(String value, SensitiveType type) { if (value==null || "".equals(value)){ return ""; } switch (type) { case DEFAULT: return value.replaceAll("^(.{3}).*$", "$1*****"); case PHONE: if (value.length()<11||value.length()>11){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); case ID_CARD: if (value.length()<18){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(\\d{4})\\d{10}(\\d{4})", "$1**********$2"); case EMAIL: if (!value.contains("@")){ return value.replaceAll("^(.{5}).*$", "$1*****"); } return value.replaceAll("(?<=^.{3}).*(?=@)", "*****"); case ADDRESS: if (value.length() <= 9) { return value.replaceAll("(.)(.*)(..)", "$1*$2$3"); } return value.replaceAll("(.{3}).*(.{3})", "$1*****$2"); case USER_NAME: return value.replaceAll("(\\S)\\S(\\S*)", "$1*$2"); default: return value; } } private boolean isPrimitiveOrWrapper(Class<?> clazz) { return clazz.isPrimitive() || clazz == Boolean.class || clazz == Integer.class || clazz == Long.class || clazz == Double.class || clazz == Float.class || clazz == Byte.class || clazz == Short.class || clazz == BigDecimal.class || clazz == Character.class; } }
logback-spring.xml配置#
<configuration> <conversionRule conversionWord="sensitive" converterClass="com.ybchen.config.SensitiveLogConverter" /> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <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>
VO类#
@Data public class UserVO { @Sensitive(type = SensitiveType.USER_NAME) private String userName; @Sensitive(type = SensitiveType.EMAIL) private String email; @Sensitive(type = SensitiveType.PHONE) private String phone; @Sensitive(type = SensitiveType.ID_CARD) private String idCard; @Sensitive(type = SensitiveType.ADDRESS) private String address; @Sensitive private Integer age; @Sensitive private BigDecimal money; @Sensitive private String content; }
打印日志#
@PostMapping("test") public Object test(@RequestBody List<UserVO> userList){ UserVO vo=new UserVO(); vo.setUserName("张三"); vo.setAddress("广东省广州市天河区xxxxxx号"); vo.setAge(18); log.info("日志脱敏----info:\r\n{}\r\n{}", userList,vo); log.info("\r\n"); log.error("日志脱敏----error:\r\n{}\r\n{}", userList,vo); System.out.println("正常获取脱敏数据 userName:"+vo.getUserName()); System.out.println("正常获取脱敏数据 address:"+vo.getAddress()); return userList; }
效果#
分类:
Spring Boot
, Spring Cloud
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2019-12-10 设计模式之☞适配器模式,通俗易懂,一学就会!!!
2019-12-10 设计模式之☞装饰模式,通俗易懂,一学就会!!!