2026/6/11 6:40:11
网站建设
项目流程
天津网站建设推广服务,四川省住房城乡建设厅网站首页,做蛋糕招聘网站,wordpress 函数教程视频大家好#xff0c;我是小悟。
一、数据脱敏#xff1a;数据界的“犹抱琵琶半遮面”
想象一下这样的场景#xff1a;你的身份证号、手机号、银行卡号这些“隐私部位”的数据#xff0c;在系统中裸奔。这简直比在公共场所穿皇帝的新衣还尴尬#xff01;数据脱敏就是给这些敏…大家好我是小悟。一、数据脱敏数据界的“犹抱琵琶半遮面”想象一下这样的场景你的身份证号、手机号、银行卡号这些“隐私部位”的数据在系统中裸奔。这简直比在公共场所穿皇帝的新衣还尴尬数据脱敏就是给这些敏感数据穿上得体的“小内裤”让它们在需要展示的时候既能完成工作又不至于春光乍泄。数据脱敏的几种常见姿势静态脱敏像给照片打马赛克一劳永逸动态脱敏像智能变色玻璃看人下菜碟前端脱敏只在展示时害羞一下后端脱敏从出生就带着面具二、SpringBoot脱敏方案实战方案1注解序列化方案给字段贴上“此处打码”标签步骤1先来个脱敏注解像给敏感部位贴标签import java.lang.annotation.*; /** * 脱敏注解给敏感字段贴上“此处需要打码”的标签 * 就像在数据身上贴了个“儿童不宜”的警示条 */ Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) Documented public interface Sensitive { /** * 脱敏类型决定怎么打码 */ SensitiveType type(); } /** * 脱敏类型枚举各种打码方式任君选择 */ public enum SensitiveType { /** 中文名张*三 */ CHINESE_NAME, /** 身份证号110**********1234 */ ID_CARD, /** 手机号138****1234 */ PHONE, /** 邮箱t***163.com */ EMAIL, /** 银行卡号6217 **** **** 1234 */ BANK_CARD, /** 地址北京市海淀区**** */ ADDRESS }步骤2实现脱敏序列化器专业的“打码师”import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.io.IOException; /** * 脱敏序列化器专业的“马赛克师傅” * 负责给敏感数据穿上得体的衣服 */ public class SensitiveSerializer extends JsonSerializerString { private final SensitiveType type; public SensitiveSerializer(SensitiveType type) { this.type type; } Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { if (value null) { gen.writeNull(); return; } // 根据脱敏类型选择不同的“打码姿势” gen.writeString(maskData(value, type)); } /** * 核心脱敏逻辑十八般武艺轮番上阵 */ private String maskData(String data, SensitiveType type) { if (data null || data.isEmpty()) { return data; } return switch (type) { case CHINESE_NAME - maskChineseName(data); case ID_CARD - maskIdCard(data); case PHONE - maskPhone(data); case EMAIL - maskEmail(data); case BANK_CARD - maskBankCard(data); case ADDRESS - maskAddress(data); default - data; // 默认不脱敏裸奔 }; } private String maskChineseName(String name) { if (name.length() 1) return name; if (name.length() 2) return name.charAt(0) *; return name.charAt(0) * name.charAt(name.length() - 1); } private String maskIdCard(String idCard) { if (idCard.length() 8) return idCard; return idCard.substring(0, 3) *.repeat(Math.max(0, idCard.length() - 7)) idCard.substring(idCard.length() - 4); } private String maskPhone(String phone) { if (phone.length() ! 11) return phone; return phone.substring(0, 3) **** phone.substring(7); } private String maskEmail(String email) { int atIndex email.indexOf(); if (atIndex 1) return email; return email.charAt(0) *** email.substring(atIndex); } private String maskBankCard(String card) { if (card.length() 8) return card; return card.substring(0, 4) **** **** card.substring(card.length() - 4); } private String maskAddress(String address) { if (address.length() 4) return address; return address.substring(0, address.length() - 4) ****; } } /** * 注解序列化器把注解和序列化器牵线搭桥 */ public class SensitiveAnnotationIntrospector extends JacksonAnnotationIntrospector { Override public Object findSerializer(Annotated am) { Sensitive sensitive am.getAnnotation(Sensitive.class); if (sensitive ! null) { return new SensitiveSerializer(sensitive.type()); } return super.findSerializer(am); } }步骤3配置Jackson告诉它“看这里要打码”import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class JacksonConfig { Bean public ObjectMapper objectMapper() { ObjectMapper mapper new ObjectMapper(); mapper.setAnnotationIntrospector(new SensitiveAnnotationIntrospector()); return mapper; } }步骤4在实体类上使用贴上标签就自动打码/** * 用户实体敏感字段都穿上了“马赛克小内裤” */ Data public class UserDTO { private Long id; Sensitive(type SensitiveType.CHINESE_NAME) private String username; Sensitive(type SensitiveType.PHONE) private String phone; Sensitive(type SensitiveType.EMAIL) private String email; Sensitive(type SensitiveType.ID_CARD) private String idCard; Sensitive(type SensitiveType.BANK_CARD) private String bankCard; Sensitive(type SensitiveType.ADDRESS) private String address; // 这个字段没注解继续裸奔 private String hobby; }步骤5控制器测试一下效果RestController RequestMapping(/user) public class UserController { GetMapping(/{id}) public UserDTO getUser(PathVariable Long id) { // 模拟从数据库查出的完整数据 UserDTO user new UserDTO(); user.setId(id); user.setUsername(张全蛋); user.setPhone(13800138000); user.setEmail(zhangquandanexample.com); user.setIdCard(110101199001011234); user.setBankCard(621700001234567890); user.setAddress(北京市海淀区中关村大街1号); user.setHobby(唱跳RAP篮球); // 返回时自动脱敏就像自动加了马赛克 return user; } }测试结果{ id: 1, username: 张*蛋, phone: 138****8000, email: z***example.com, idCard: 110**********1234, bankCard: 6217 **** **** 7890, address: 北京市海淀区中关村大街****, hobby: 唱跳RAP篮球 }方案2AOP切面方案数据出门前的安检员步骤1定义脱敏策略接口/** * 脱敏策略定义各种脱敏算法 * 就像不同的美颜滤镜 */ public interface SensitiveStrategy { String mask(String data); } /** * 策略工厂根据类型选择合适的滤镜 */ Component public class SensitiveStrategyFactory { private final MapSensitiveType, SensitiveStrategy strategies new HashMap(); public SensitiveStrategyFactory() { // 注册各种美颜滤镜 strategies.put(SensitiveType.CHINESE_NAME, new ChineseNameStrategy()); strategies.put(SensitiveType.PHONE, new PhoneStrategy()); strategies.put(SensitiveType.ID_CARD, new IdCardStrategy()); // ... 其他策略 } public SensitiveStrategy getStrategy(SensitiveType type) { return strategies.getOrDefault(type, data - data); } // 具体策略实现 private static class ChineseNameStrategy implements SensitiveStrategy { Override public String mask(String data) { if (data null || data.length() 1) return data; if (data.length() 2) return data.charAt(0) *; return data.charAt(0) * data.charAt(data.length() - 1); } } private static class PhoneStrategy implements SensitiveStrategy { Override public String mask(String data) { if (data null || data.length() ! 11) return data; return data.substring(0, 3) **** data.substring(7); } } // ... 其他策略实现 }步骤2AOP切面实现Aspect Component Slf4j public class SensitiveAspect { Autowired private SensitiveStrategyFactory strategyFactory; /** * 拦截所有Controller方法返回 * 就像在数据出门前设了个安检门 */ Around(annotation(org.springframework.web.bind.annotation.GetMapping) || annotation(org.springframework.web.bind.annotation.PostMapping) || annotation(org.springframework.web.bind.annotation.RequestMapping)) public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable { // 放行方法执行 Object result joinPoint.proceed(); // 给返回结果穿上衣服 return processSensitiveData(result); } /** * 递归处理脱敏连数据对象的子孙后代都不放过 */ private Object processSensitiveData(Object obj) { if (obj null) return null; // 如果是集合给每个元素都穿上衣服 if (obj instanceof Collection) { return processCollection((Collection?) obj); } // 如果是数组也不放过 if (obj.getClass().isArray()) { return processArray((Object[]) obj); } // 如果是Map处理每个值 if (obj instanceof Map) { return processMap((Map?, ?) obj); } // 如果是普通对象深度扫描敏感字段 if (isCustomClass(obj.getClass())) { return processObject(obj); } // 基本类型直接返回 return obj; } private Object processObject(Object obj) { Class? clazz obj.getClass(); Object newObj; try { newObj clazz.newInstance(); } catch (Exception e) { log.warn(创建对象实例失败: {}, clazz.getName()); return obj; } // 反射获取所有字段 Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); try { Object value field.get(obj); // 如果有脱敏注解穿上马赛克 Sensitive sensitive field.getAnnotation(Sensitive.class); if (sensitive ! null value instanceof String) { SensitiveStrategy strategy strategyFactory.getStrategy(sensitive.type()); value strategy.mask((String) value); } else if (value ! null) { // 递归处理嵌套对象 value processSensitiveData(value); } field.set(newObj, value); } catch (Exception e) { log.warn(处理字段 {} 失败, field.getName(), e); } } return newObj; } }方案3MyBatis拦截器方案数据库查询时的美颜相机/** * MyBatis拦截器在数据从数据库出来时实时美颜 */ Intercepts({ Signature(type ResultSetHandler.class, method handleResultSets, args {Statement.class}) }) Component Slf4j public class SensitiveInterceptor implements Interceptor { Autowired private SensitiveStrategyFactory strategyFactory; Override public Object intercept(Invocation invocation) throws Throwable { // 先执行原方法获取结果 Object result invocation.proceed(); if (result null) { return null; } // 处理结果集 if (result instanceof List) { for (Object obj : (List?) result) { processObject(obj); } } else { processObject(result); } return result; } private void processObject(Object obj) { if (obj null) return; Class? clazz obj.getClass(); Field[] fields clazz.getDeclaredFields(); for (Field field : fields) { Sensitive sensitive field.getAnnotation(Sensitive.class); if (sensitive ! null) { field.setAccessible(true); try { Object value field.get(obj); if (value instanceof String) { SensitiveStrategy strategy strategyFactory.getStrategy(sensitive.type()); String maskedValue strategy.mask((String) value); field.set(obj, maskedValue); } } catch (Exception e) { log.error(脱敏处理失败, e); } } } } Override public Object plugin(Object target) { return Plugin.wrap(target, this); } Override public void setProperties(Properties properties) { // 可以配置一些属性 } }方案4自定义消息转换器方案HTTP出口处的安检机/** * 自定义HTTP消息转换器在数据离开系统前最后一道安检 */ Component public class SensitiveHttpMessageConverter extends MappingJackson2HttpMessageConverter { Autowired private SensitiveStrategyFactory strategyFactory; Override protected void writeInternal(Object object, Type type, HttpOutputMessage outputMessage) throws IOException { // 先脱敏再序列化 Object processedObject processSensitiveData(object); super.writeInternal(processedObject, type, outputMessage); } // 脱敏处理方法同上省略重复代码 private Object processSensitiveData(Object obj) { // 实现同AOP方案中的processSensitiveData方法 // ... } }方案5数据库层脱敏方案给数据库戴上口罩/** * Hibernate事件监听器数据入库时自动加密出库时自动解密 */ Component public class SensitiveEventListener implements PostLoadEventListener, PreInsertEventListener, PreUpdateEventListener { Autowired private EncryptionService encryptionService; Override public void onPostLoad(PostLoadEvent event) { Object entity event.getEntity(); // 加载后解密 decryptEntity(entity); } Override public boolean onPreInsert(PreInsertEvent event) { // 插入前加密 encryptEntity(event.getEntity()); return false; } Override public boolean onPreUpdate(PreUpdateEvent event) { // 更新前加密 encryptEntity(event.getEntity()); return false; } private void encryptEntity(Object entity) { if (entity null) return; Field[] fields entity.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(SensitiveEncrypt.class)) { field.setAccessible(true); try { Object value field.get(entity); if (value instanceof String) { String encrypted encryptionService.encrypt((String) value); field.set(entity, encrypted); } } catch (Exception e) { log.error(加密字段失败, e); } } } } private void decryptEntity(Object entity) { // 类似encryptEntity调用encryptionService.decrypt } }三、脱敏方案选择指南对症下药1.注解序列化方案适用场景REST API返回数据脱敏优点简单优雅与业务解耦缺点只对JSON序列化有效2.AOP切面方案适用场景需要对Controller层统一处理优点集中管理支持复杂逻辑缺点性能开销可能误伤3.MyBatis拦截器方案适用场景数据库查询结果脱敏优点从源头控制一劳永逸缺点影响所有查询不够灵活4.自定义消息转换器方案适用场景全局HTTP响应处理优点最彻底的出口控制缺点可能与其他组件冲突5.数据库层方案适用场景存储加密展示脱敏优点最安全防止数据泄露缺点影响查询性能实现复杂四、最佳实践建议1.分层防御不要把所有鸡蛋放在一个篮子里数据安全防护体系 - 存储层加密存储最后的底线 - 业务层逻辑脱敏灵活控制 - 展示层展示脱敏用户体验2.配置化脱敏像调美颜强度一样可配置Component ConfigurationProperties(prefix sensitive) Data public class SensitiveProperties { /** * 是否开启脱敏 */ private boolean enabled true; /** * 脱敏规则配置 */ private MapSensitiveType, Rule rules new HashMap(); Data public static class Rule { /** * 保留前几位 */ private Integer keepPrefix 3; /** * 保留后几位 */ private Integer keepSuffix 4; /** * 替换字符 */ private Character maskChar *; } }3.性能优化脱敏也要注意效率Component public class SensitiveCache { private final CacheString, String cache Caffeine.newBuilder() .maximumSize(10000) .expireAfterWrite(5, TimeUnit.MINUTES) .build(); public String maskWithCache(String data, SensitiveType type, SensitiveStrategy strategy) { String key type.name() : data; return cache.get(key, k - strategy.mask(data)); } }4.监控与日志知道谁在什么时候脱敏Aspect Component Slf4j public class SensitiveMonitorAspect { Around(annotation(org.springframework.web.bind.annotation.RequestMapping)) public Object monitorSensitive(ProceedingJoinPoint joinPoint) throws Throwable { long start System.currentTimeMillis(); Object result joinPoint.proceed(); long cost System.currentTimeMillis() - start; // 记录脱敏统计 log.info(脱敏处理完成方法{}耗时{}ms, joinPoint.getSignature(), cost); return result; } }五、总结数据脱敏的智慧数据脱敏就像给敏感数据穿上得体的衣服——既不能裸奔安全风险也不能裹成木乃伊影响使用。通过SpringBoot的各种方案我们可以因地制宜根据不同的场景选择合适的脱敏方案层层设防构建多层次的数据安全防护体系灵活配置像调节美颜相机一样轻松调整脱敏策略性能平衡在安全和性能之间找到最佳平衡点没有一种方案是万能的。就像穿衣服要分场合泳池穿泳衣会议室穿正装数据脱敏也要根据具体场景选择最合适的方案。最终目标让敏感数据既能保守秘密又能履行职责。毕竟数据的价值在于使用而不是锁在保险柜里吃灰。脱敏就是让数据在安全和可用之间优雅地走钢丝// 最后送大家一个万能脱敏方法 public String universalMask(String data) { return ****; // 简单粗暴但最安全开玩笑的别真用 }过多的脱敏会影响业务过少的脱敏又存在风险。找到那个刚刚好的平衡点才是数据脱敏的最高境界谢谢你看我的文章既然看到这里了如果觉得不错随手点个赞、转发、在看三连吧感谢感谢。那我们下次再见。您的一键三连是我更新的最大动力谢谢山水有相逢来日皆可期谢谢阅读我们再会我手中的金箍棒上能通天下能探海