news 2026/2/7 3:05:55

SpringBoot 中 6 种数据脱敏方案,第 5 种太强了,支持深度递归!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot 中 6 种数据脱敏方案,第 5 种太强了,支持深度递归!

前言

大家好! 在日常的开发开发工作中,我相信各位老铁肯定遇到过这种需求:“手机号中间四位得用*显示”、“身份证中间八位要隐藏”、“用户邮箱前缀脱敏”……例如:

  • 手机号:13812345678138****5678
  • 身份证:430101199003078888430101********8888
  • 姓名:张三四张*四
  • 邮箱号:12345678@qq.com1234****@qq.com
  • 银行卡:62303518888524056230********2405

既要展示部分数据,又要保证敏感信息不泄露。这就是所谓的数据脱敏

今天给大家分享6种我在项目中常用的脱敏方案,SpringBoot项目拿来即用,可以直接复制粘贴!

方案一:Hutool工具库(懒人必备!)

不想造轮子可以直接用现成的!

java

体验AI代码助手

代码解读

复制代码

// Maven依赖 <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.0</version> </dependency> // 使用示例 String phone = "13812345678"; String maskedPhone = DesensitizedUtil.mobilePhone(phone); // 138****5678 String idCard = "430101199003078888"; String maskedIdCard = DesensitizedUtil.idCardNum(idCard, 6, 4); // 430101********8888 String name = "张三四"; String maskedName = DesensitizedUtil.chineseName(name); // 张*四 String email = "12345678@qq.com"; String maskedEmail = DesensitizedUtil.email(email); // 1234****@qq.com String bankCard = "6230351888852405"; String maskedBankCard = DesensitizedUtil.bankCard(bankCard); // 6230********2405

功能全面,开箱即用

方案二:正则工具类

写个工具类,需要的时候手动处理一下,这是最简单直接的方式。

java

体验AI代码助手

代码解读

复制代码

/** * 脱敏工具类 - 简单直接 */ public class SensitiveUtil { /** * 手机号脱敏:13812345678 -> 138****5678 */ public static String maskPhone(String phone) { if (StringUtils.isEmpty(phone) || phone.length() != 11) { return phone; } return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); } /** * 身份证脱敏:430101199003078888 -> 430101********8888 */ public static String maskIdCard(String idCard) { if (StringUtils.isEmpty(idCard) || idCard.length() < 15) { return idCard; } return idCard.replaceAll("(\\d{6})\\d{8}(\\w{4})", "$1********$2"); } /** * 姓名脱敏:张三四 -> 张*四 */ public static String maskName(String name) { if (StringUtils.isEmpty(name)) { return name; } if (name.length() == 1) { return "*"; } if (name.length() == 2) { return name.charAt(0) + "*"; } return name.substring(0, 1) + "*" + name.substring(name.length() - 1); } /** * 邮箱脱敏:12345678@qq.com -> 1234****@qq.com */ public static String maskEmail(String email) { if (StringUtils.isEmpty(email) || !email.contains("@")) { return email; } String[] parts = email.split("@"); if (parts[0].length() <= 4) { return parts[0].substring(0, 1) + "****@" + parts[1]; } return parts[0].substring(0, 4) + "****@" + parts[1]; } /** * 银行卡脱敏:6230351888852405 -> 6230********2405 */ public static String maskBankCard(String bankCard) { if (StringUtils.isEmpty(bankCard) || bankCard.length() < 8) { return bankCard; } return bankCard.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1********$2"); } }

怎么用?很简单:

java

体验AI代码助手

代码解读

复制代码

User user = userService.getById(123); // 手动脱敏 user.setPhone(SensitiveUtil.maskPhone(user.getPhone())); user.setIdCard(SensitiveUtil.maskIdCard(user.getIdCard())); user.setName(SensitiveUtil.maskName(user.getName())); user.setEmail(SensitiveUtil.maskEmail(user.getEmail())); user.setBankCard(SensitiveUtil.maskBankCard(user.getBankCard())); return user;

非常简单易懂,数据脱敏不再是问题。

但有些朋友又说了:“每写一个接口或者每一个字段都要单独调用,有点心累。有没有更方便的方案?” 请看方案二。

方案三:自定义注解 + Jackson

利用Jackson的序列化机制,在返回JSON时自动对标注了注解的字段进行脱敏。

java

体验AI代码助手

代码解读

复制代码

// 脱敏注解 @Retention(RetentionPolicy.RUNTIME) @JacksonAnnotationsInside @JsonSerialize(using = SensitiveSerializer.class) public @interface Sensitive { SensitiveType value(); } // 脱敏类型枚举 public enum SensitiveType { PHONE, // 手机号 ID_CARD, // 身份证 NAME, // 姓名 EMAIL, // 邮箱 BANK_CARD // 银行卡 } // 脱敏序列化器 public class SensitiveSerializer extends JsonSerializer<String> { private SensitiveType type; public SensitiveSerializer(SensitiveType type) { this.type = type; } @Override public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { String result; switch (type) { case PHONE: result = SensitiveUtil.maskPhone(value); break; case ID_CARD: result = SensitiveUtil.maskIdCard(value); break; case NAME: result = SensitiveUtil.maskName(value); break; case EMAIL: result = SensitiveUtil.maskEmail(value); break; case BANK_CARD: result = SensitiveUtil.maskBankCard(value); break; default: result = value; } gen.writeString(result); } }

使用方式

java

体验AI代码助手

代码解读

复制代码

public class User { private String name; @Sensitive(SensitiveType.PHONE) private String phone; @Sensitive(SensitiveType.ID_CARD) private String idCard; @Sensitive(SensitiveType.NAME) private String name; @Sensitive(SensitiveType.EMAIL) private String email; @Sensitive(SensitiveType.BANK_CARD) private String bankCard; }

性能好,只在序列化时生效。配置一次,全局都可以用,不影响业务逻辑。缺点是需要配置Jackson

方案四:Lombok + 自定义Getter(轻量级替代方案)

如果你不想引入太多框架,但用了Lombok,这个方案很合适。

思路:让Lombok不生成默认getter,我们自己写一个带脱敏逻辑的getter

1. 关闭Lombok的默认getter

去掉@Data,只保留你需要的Lombok注解,手动添加带脱敏的getter

java

体验AI代码助手

代码解读

复制代码

@Builder @NoArgsConstructor @AllArgsConstructor @Setter public class UserVO { private String name; private String phone; private String idCard; private String email; private String bankCard; // 自定义脱敏 getter public String getName() { return SensitiveUtil.maskName(name); } public String getPhone() { return SensitiveUtil.maskPhone(phone); } public String getIdCard() { return SensitiveUtil.maskIdCard(idCard); } public String getEmail() { return SensitiveUtil.maskEmail(email); } public String getBankCard() { return SensitiveUtil.maskBankCard(bankCard); } }

这样只要调用user.getxx(),返回的就是脱敏后的数据。

实现简单,不依赖额外组件,适合小项目。但每个字段都要手动写getter,有点啰嗦了。

方案五:AOP切面深度脱敏(推荐)

这个是主推的方案,特别适合嵌套对象、ListMap等类型。 为什么?因为前面的方法大多只处理单层DTO
但现实中经常是:

java

体验AI代码助手

代码解读

复制代码

public class User { @Sensitive(SensitiveType.PHONE) private String phone; // 这个能脱敏 private UserDetail detail; // 这里面还有敏感字段 } public class UserDetail { @Sensitive(SensitiveType.ID_CARD) private String idCard; // 这个脱敏不了! }

返回User对象时,phone字段能脱敏,但detail.idCard还是明文显示!

解决方案:用AOP+ 深度递归反射。详细步骤如下:

1. 定义脱敏类型枚举

java

体验AI代码助手

代码解读

复制代码

/** * 脱敏类型枚举 */ public enum SensitiveType { PHONE, // 手机号 ID_CARD, // 身份证 NAME, // 姓名 EMAIL, // 邮箱 BANK_CARD // 银行卡 }

2. 定义注解@Sensitive

java

体验AI代码助手

代码解读

复制代码

/** * 脱敏注解 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Sensitive { SensitiveType value(); }

3. 需要保留方案一的工具类,增加以下的maskByType方法

java

体验AI代码助手

代码解读

复制代码

/** * 根据类型脱敏 */ public static String maskByType(String value, SensitiveType type) { if (StringUtils.isBlank(value)) { return value; } switch (type) { case PHONE: return maskPhone(value); case ID_CARD: return maskIdCard(value); case NAME: return maskName(value); case EMAIL: return maskEmail(value); case BANK_CARD: return maskBankCard(value); default: return value; } } }

4. 写AOP切面(核心逻辑)

java

体验AI代码助手

代码解读

复制代码

/** * 深度脱敏AOP处理器 */ @Aspect @Component @Slf4j public class DeepSensitiveAspect { // 定义切点:拦截Controller层所有方法 @Pointcut("execution(* com.example.controller..*.*(..))") public void controllerPointcut() {} // 定义切点:拦截Service层所有方法 @Pointcut("execution(* com.example.service..*.*(..))") public void servicePointcut() {} /** * 环绕通知:处理Controller层返回结果 */ @Around("controllerPointcut()") public Object aroundController(ProceedingJoinPoint joinPoint) throws Throwable { Object result = joinPoint.proceed(); return processDeepSensitive(result); } /** * 环绕通知:处理Service层返回结果 */ @Around("servicePointcut()") public Object aroundService(ProceedingJoinPoint joinPoint) throws Throwable { Object result = joinPoint.proceed(); return processDeepSensitive(result); } /** * 深度脱敏处理 */ private Object processDeepSensitive(Object obj) { if (obj == null) { return null; } // 处理集合类型 if (obj instanceof List) { return processList((List<?>) obj); } // 处理数组类型 if (obj.getClass().isArray()) { return processArray((Object[]) obj); } // 处理Map类型 if (obj instanceof Map) { return processMap((Map<?, ?>) obj); } // 处理分页对象(Spring Data Page) if (obj instanceof Page) { return processPage((Page<?>) obj); } // 处理普通Java对象 if (isCustomClass(obj.getClass())) { return processObject(obj); } // 基本类型直接返回 return obj; } /** * 处理List集合 */ private List<?> processList(List<?> list) { if (list == null || list.isEmpty()) { return list; } return list.stream() .map(this::processDeepSensitive) .collect(Collectors.toList()); } /** * 处理数组 */ private Object[] processArray(Object[] array) { if (array == null || array.length == 0) { return array; } Object[] result = new Object[array.length]; for (int i = 0; i < array.length; i++) { result[i] = processDeepSensitive(array[i]); } return result; } /** * 处理Map */ private Map<?, ?> processMap(Map<?, ?> map) { if (map == null || map.isEmpty()) { return map; } Map<Object, Object> result = new HashMap<>(); for (Map.Entry<?, ?> entry : map.entrySet()) { result.put(entry.getKey(), processDeepSensitive(entry.getValue())); } return result; } /** * 处理分页对象 */ private Page<?> processPage(Page<?> page) { if (page == null) { return null; } List<?> content = processList(page.getContent()); return new PageImpl<>(content, page.getPageable(), page.getTotalElements()); } /** * 处理单个对象 */ private Object processObject(Object obj) { if (obj == null) { return null; } Class<?> clazz = obj.getClass(); try { // 获取所有字段(包括父类) List<Field> fields = getAllFields(clazz); for (Field field : fields) { field.setAccessible(true); // 检查是否有脱敏注解 Sensitive sensitive = field.getAnnotation(Sensitive.class); if (sensitive != null && field.getType() == String.class) { // 处理敏感字段 processSensitiveField(obj, field, sensitive); } else { // 递归处理嵌套对象 processNestedField(obj, field); } } } catch (Exception e) { log.warn("脱敏处理失败: {}", e.getMessage()); } return obj; } /** * 处理敏感字段 */ private void processSensitiveField(Object obj, Field field, Sensitive sensitive) { try { String value = (String) field.get(obj); if (StringUtils.isNotBlank(value)) { String maskedValue = SensitiveUtil.maskByType(value, sensitive.value()); field.set(obj, maskedValue); } } catch (IllegalAccessException e) { log.warn("字段脱敏失败: {}", field.getName()); } } /** * 处理嵌套字段 */ private void processNestedField(Object obj, Field field) { try { Object fieldValue = field.get(obj); if (fieldValue != null && isCustomClass(field.getType())) { // 递归处理嵌套对象 processObject(fieldValue); } } catch (IllegalAccessException e) { // 忽略无法访问的字段 } } /** * 获取所有字段(包括父类) */ private List<Field> getAllFields(Class<?> clazz) { List<Field> fields = new ArrayList<>(); while (clazz != null && clazz != Object.class) { fields.addAll(Arrays.asList(clazz.getDeclaredFields())); clazz = clazz.getSuperclass(); } return fields; } /** * 判断是否为自定义类(非JDK类) */ private boolean isCustomClass(Class<?> clazz) { return clazz != null && !clazz.isPrimitive() && !clazz.getName().startsWith("java.") && !clazz.getName().startsWith("javax."); } }

5. 实体类使用示例

java

体验AI代码助手

代码解读

复制代码

/** * 用户实体 */ @Data public class User { private Long id; @Sensitive(SensitiveType.NAME) private String name; @Sensitive(SensitiveType.PHONE) private String phone; @Sensitive(SensitiveType.ID_CARD) private String idCard; @Sensitive(SensitiveType.EMAIL) private String email; @Sensitive(SensitiveType.BANK_CARD) private String bankCard; // 嵌套对象也会被处理 private UserDetail detail; } /** * 用户详情实体 */ @Data public class UserDetail { @Sensitive(SensitiveType.PHONE) private String emergencyPhone; @Sensitive(SensitiveType.ID_CARD) private String spouseIdCard; }

6. Controller使用示例

java

体验AI代码助手

代码解读

复制代码

@RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; /** * 返回单个用户(自动脱敏) */ @GetMapping("/{id}") public User getUser(@PathVariable Long id) { return userService.getUserById(id); } /** * 返回用户列表(自动脱敏) */ @GetMapping public List<User> getUsers() { return userService.getAllUsers(); } /** * 返回分页数据(自动脱敏) */ @GetMapping("/page") public Page<User> getUsersByPage(Pageable pageable) { return userService.getUsersByPage(pageable); } }

7. 效果验证

json

体验AI代码助手

代码解读

复制代码

{ "id": 1, "name": "张*四", "phone": "138****5678", "idCard": "430101********8888", "email": "1234****@qq.com", "bankCard": "6230********2405", "detail": { "emergencyPhone": "139****8765", "spouseIdCard": "110105********1234" } }

优点

  • 全面支持:手机号、身份证、姓名、邮箱、银行卡全搞定
  • 深度处理:支持多层嵌套对象脱敏
  • 零侵入:业务代码无需任何修改
  • 高性能:使用反射缓存,性能优化
  • 易扩展:新增脱敏类型只需扩展枚举和工具方法

方案六:Mysql数据库层脱敏

最简单的MySQL脱敏SQL

sql

体验AI代码助手

代码解读

复制代码

SELECT id, CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone, CONCAT(LEFT(id_card, 6), '********', RIGHT(id_card, 4)) AS id_card, CONCAT(LEFT(name, 1), '*', RIGHT(name, 1)) AS name, CONCAT(LEFT(email, 4), '****', SUBSTRING(email, LOCATE('@', email))) AS email, CONCAT(LEFT(bank_card, 4), '********', RIGHT(bank_card, 4)) AS bank_card FROM users;

分字段详细写法

1. 手机号脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone FROM users; -- 13812345678 → 138****5678

2. 身份证脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT CONCAT(LEFT(id_card, 6), '********', RIGHT(id_card, 4)) AS id_card FROM users; -- 430101199003078888 → 430101********8888

3. 姓名脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT CASE WHEN LENGTH(name) = 1 THEN '*' WHEN LENGTH(name) = 2 THEN CONCAT(LEFT(name, 1), '*') ELSE CONCAT(LEFT(name, 1), '*', RIGHT(name, 1)) END AS name FROM users; -- 张三四 → 张*四

4. 邮箱脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT CONCAT(LEFT(email, 4), '****', SUBSTRING(email, LOCATE('@', email))) AS email FROM users; -- 12345678@qq.com → 1234****@qq.com

5. 银行卡脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT CONCAT(LEFT(bank_card, 4), '********', RIGHT(bank_card, 4)) AS bank_card FROM users; -- 6230351888852405 → 6230********2405

6. 完整查询示例

sql

体验AI代码助手

代码解读

复制代码

-- 查询单个用户 SELECT id, CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone, CONCAT(LEFT(id_card, 6), '********', RIGHT(id_card, 4)) AS id_card, CASE WHEN LENGTH(name) = 1 THEN '*' WHEN LENGTH(name) = 2 THEN CONCAT(LEFT(name, 1), '*') ELSE CONCAT(LEFT(name, 1), '*', RIGHT(name, 1)) END AS name, CONCAT(LEFT(email, 4), '****', SUBSTRING(email, LOCATE('@', email))) AS email, CONCAT(LEFT(bank_card, 4), '********', RIGHT(bank_card, 4)) AS bank_card FROM users WHERE id = 1; -- 查询用户列表 SELECT id, CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone, CONCAT(LEFT(name, 1), '*', RIGHT(name, 1)) AS name FROM users ORDER BY id DESC LIMIT 10;

7. 创建视图方案

如果经常要用,建个视图更方便:

sql

体验AI代码助手

代码解读

复制代码

CREATE VIEW v_user_masked AS SELECT id, CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4)) AS phone, CONCAT(LEFT(id_card, 6), '********', RIGHT(id_card, 4)) AS id_card, CASE WHEN LENGTH(name) = 1 THEN '*' WHEN LENGTH(name) = 2 THEN CONCAT(LEFT(name, 1), '*') ELSE CONCAT(LEFT(name, 1), '*', RIGHT(name, 1)) END AS name, CONCAT(LEFT(email, 4), '****', SUBSTRING(email, LOCATE('@', email))) AS email, CONCAT(LEFT(bank_card, 4), '********', RIGHT(bank_card, 4)) AS bank_card FROM users; -- 直接用视图查询 SELECT * FROM v_user_masked WHERE id = 1;

8. 联表查询脱敏

sql

体验AI代码助手

代码解读

复制代码

SELECT u.id, CONCAT(LEFT(u.name, 1), '*', RIGHT(u.name, 1)) AS user_name, CONCAT(LEFT(u.phone, 3), '****', RIGHT(u.phone, 4)) AS user_phone, o.order_no, o.amount FROM users u INNER JOIN orders o ON u.id = o.user_id;

最后总结

方案优点缺点适用场景
Hutool 工具库开箱即用,代码简洁,功能全依赖第三方库,灵活性有限快速开发、小项目、原型阶段
正则工具类 + 手动调用简单直接,无额外依赖需手动调用,侵入业务代码单字段少量脱敏,轻量级应用
自定义注解 + Jackson 序列化自动化序列化脱敏,性能好仅作用于 JSON 输出,不支持嵌套对象深层脱敏REST API 返回数据脱敏
Lombok + 自定义 Getter轻量,无需切面或框架每个字段都要写 getter,重复代码多VO/DTO 层控制输出,适合简单结构
AOP 切面深度脱敏(推荐)支持嵌套对象、集合、分页;零侵入业务;可全局生效;易扩展实现复杂,需理解反射和 AOP中大型项目,复杂数据结构,统一脱敏治理
MySQL 数据库层脱敏不依赖 Java 层,查询即脱敏,安全隔离SQL 复杂,维护成本高,无法动态控制报表查询、只读视图、DBA 视角脱敏

🎯最佳实践建议

1.如果你是新手或者小项目起步,方案一(Hutool)方案二(工具类)

2.如果你做的是标准后端服务API,推荐方案三(Jackson 注解序列化)结合@Sensitive注解,在返回 JSON时自动脱敏。

3.如果你的项目结构复杂、嵌套深、List/Map/分页多,强烈推荐方案五(AOP深度脱敏)!这是真正意义上的“一次配置,处处脱敏”。

4.如果你有DB权限且需要给前端/报表提供固定脱敏视图,使用方案六(MySQL脱敏+视图),实现数据访问层面的安全隔离。

希望这篇文章能帮你在实际项目中轻松搞定数据脱敏问题。如果觉得有用,欢迎点赞、收藏、转发,让更多人看到!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/30 19:52:09

Node.js数据库操作终极指南:better-sqlite3深度解析

Node.js数据库操作终极指南&#xff1a;better-sqlite3深度解析 【免费下载链接】better-sqlite3 The fastest and simplest library for SQLite3 in Node.js. 项目地址: https://gitcode.com/gh_mirrors/be/better-sqlite3 better-sqlite3是Node.js生态中性能最卓越的S…

作者头像 李华
网站建设 2026/1/30 12:15:34

跨设备文件传输的完整指南:如何实现高效数据流转

跨设备文件传输的完整指南&#xff1a;如何实现高效数据流转 【免费下载链接】WindSend Quickly and securely sync clipboard, transfer files and directories between devices. 快速安全的同步剪切板&#xff0c;传输文件或文件夹 项目地址: https://gitcode.com/gh_mirro…

作者头像 李华
网站建设 2026/2/5 17:46:19

Zenject框架:Unity游戏架构的终极解决方案

Zenject框架&#xff1a;Unity游戏架构的终极解决方案 【免费下载链接】Zenject 项目地址: https://gitcode.com/gh_mirrors/zen/Zenject 在Unity游戏开发中&#xff0c;你是否经常面临这样的困境&#xff1a;代码耦合度过高导致维护困难&#xff0c;新功能开发举步维艰…

作者头像 李华
网站建设 2026/2/5 23:16:44

围棋AI训练终极指南:从零基础到高手进阶的完整路径

围棋AI训练终极指南&#xff1a;从零基础到高手进阶的完整路径 【免费下载链接】katrain Improve your Baduk skills by training with KataGo! 项目地址: https://gitcode.com/gh_mirrors/ka/katrain 想要在围棋对弈中快速突破瓶颈&#xff1f;围棋AI训练已经成为现代棋…

作者头像 李华
网站建设 2026/2/4 5:18:31

Moonlight for Tizen:让三星智能电视变身免费游戏主机

Moonlight for Tizen&#xff1a;让三星智能电视变身免费游戏主机 【免费下载链接】moonlight-chrome-tizen A WASM port of Moonlight for Samsung Smart TVs running Tizen OS (5.5 and up) 项目地址: https://gitcode.com/gh_mirrors/mo/moonlight-chrome-tizen 还在…

作者头像 李华