当前位置: 首页 > news >正文

SpringBoot项目数据脱敏(自定义注解)

文章目录

  • 前言
  • 一.配置
    • 1.脱敏类型枚举:DesensitizeType
    • 2.注解:Desensitize
    • 3.序列化类:DesensitizeJsonSerializer
    • 4.工具类:DesensitizeUtil
  • 二、测试:DesensitizeTest
  • 三、效果展示
  • 总结

前言

在互联网应用中,用户隐私数据(如姓名、身份证号、手机号、邮箱等)的保护至关重要。为了防止敏感信息在日志、接口返回、前端展示等环节泄露,数据脱敏 成为系统开发中的必备功能

一.配置

1.脱敏类型枚举:DesensitizeType

import lombok.Getter;
/*** 脱敏类型枚举*/
@Getter
public enum DesensitizeType {NAME(0, "中文姓名"),ID_CARD(1, "身份证号"),EMAIL(2, "邮箱"),PHONE(3, "手机号"),CUSTOM(4, "自定义");private final int code;private final String description;DesensitizeType(int code, String description) {this.code = code;this.description = description;}/*** 通过 code 反查枚举*/public static DesensitizeType fromCode(int code) {for (DesensitizeType type : values()) {if (type.getCode() == code) {return type;}}throw new IllegalArgumentException("未知的脱敏类型 code: " + code);}
}

使用 @Getter 自动生成 getter,fromCode 支持通过 code 查找枚举,便于后续扩展配置化脱敏策略

2.注解:Desensitize

import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fc.enums.DesensitizeType;
import com.fc.serializer.DesensitizeJsonSerializer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** 自定义数据脱敏注解*/
@Target(ElementType.FIELD)//用于字段
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeJsonSerializer.class) // 该注解使用序列化的方式
public @interface Desensitize {DesensitizeType type();//脱敏数据类型(必须指定类型)int prefixNoMaskLen() default 3; // 手机号通常保留前3位int suffixNoMaskLen() default 4; // 手机号通常保留后4位String symbol() default "*";//替换符号
}

3.序列化类:DesensitizeJsonSerializer

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import com.fc.utils.DesensitizeUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.EnumMap;
import java.util.Objects;
import java.util.function.UnaryOperator;
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DesensitizeJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {private DesensitizeType type;private int prefixLen;private int suffixLen;private String mask;/* 策略表:DesensitizeType → 函数 */private static final EnumMap<DesensitizeType, UnaryOperator<String>> STRATEGY = new EnumMap<>(DesensitizeType.class);static {STRATEGY.put(DesensitizeType.NAME, DesensitizeUtil::hideName);STRATEGY.put(DesensitizeType.ID_CARD, DesensitizeUtil::hideIdCard);STRATEGY.put(DesensitizeType.EMAIL, DesensitizeUtil::hideEmail);STRATEGY.put(DesensitizeType.PHONE, DesensitizeUtil::hidePhone);}@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers)throws IOException {if (value == null) {                // 1) 空值透传gen.writeNull();return;}if (type == DesensitizeType.CUSTOM) {gen.writeString(DesensitizeUtil.customMask(value, prefixLen, suffixLen, mask));} else {UnaryOperator<String> func = STRATEGY.get(type);if (func == null) {throw new IllegalArgumentException("未注册的策略:" + type);}gen.writeString(func.apply(value));}}/* 读取注解信息,构造专属序列化器 */@Overridepublic JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property)throws JsonMappingException {if (property != null && Objects.equals(property.getType().getRawClass(), String.class)) {Desensitize anno = property.getAnnotation(Desensitize.class);if (anno != null) {return new DesensitizeJsonSerializer(anno.type(), anno.prefixNoMaskLen(), anno.suffixNoMaskLen(), anno.symbol());}}return prov.findValueSerializer(property.getType(), property);}
}

ContextualSerializer 允许在序列化前读取字段的注解信息。
每个带 @Desensitize 的字段都会生成一个“专属”序列化器,携带注解参数。
使用 EnumMap 存储策略,性能优于 if-else 或 switch。

4.工具类:DesensitizeUtil

import org.apache.commons.lang.StringUtils;
import java.util.regex.Pattern;
/*** 脱敏工具类*/
public class DesensitizeUtil {/* 预编译正则表达式(提升性能) */private static final Pattern PHONE_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");private static final Pattern EMAIL_PATTERN = Pattern.compile("(^.)[^@]*(@.*$)");private static final Pattern ID_CARD_PATTERN = Pattern.compile("(\\d{4})\\d{10}(\\w{4})");/*** 中文姓名:张三 → 张**/public static String hideName(String name) {if (StringUtils.isBlank(name)) return name;return name.charAt(0) + "*".repeat(Math.max(0, name.length() - 1));}/*** 手机号:13812345678 → 138****5678*/public static String hidePhone(String phone) {if (StringUtils.isBlank(phone)) return phone;return PHONE_PATTERN.matcher(phone).replaceFirst("$1****$2");}/*** 邮箱:zhangsan@163.com → z****@163.com*/public static String hideEmail(String email) {if (StringUtils.isBlank(email)) return email;return EMAIL_PATTERN.matcher(email).replaceFirst("$1****$2");}/*** 身份证:123456789012345678 → 1234****5678*/public static String hideIdCard(String idCard) {if (StringUtils.isBlank(idCard)) return idCard;return ID_CARD_PATTERN.matcher(idCard).replaceFirst("$1****$2");}/*** 通用脱敏:保留前后指定长度,中间用符号填充*/public static String customMask(String origin, int prefix, int suffix, String mask) {if (origin == null || origin.isEmpty()) return origin;int len = origin.length();if (prefix + suffix >= len) return origin; // 不脱敏String prefixStr = origin.substring(0, prefix);String suffixStr = origin.substring(len - suffix);String masked = mask.repeat(len - prefix - suffix);return prefixStr + masked + suffixStr;}
}

使用 StringUtils.isBlank 来安全判断空值;正则预编译提升性能

二、测试:DesensitizeTest

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import lombok.Builder;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
public class DesensitizeTest {@Data@Builderpublic static class UserVO {private Long id;private Long roleId;@Desensitize(type = DesensitizeType.ID_CARD)private String idCard;@Desensitize(type = DesensitizeType.NAME)private String name;@Desensitize(type = DesensitizeType.PHONE)private String phoneNumber;@Desensitize(type = DesensitizeType.EMAIL)private String email;@Desensitize(type = DesensitizeType.CUSTOM, prefixNoMaskLen = 2, suffixNoMaskLen = 2, symbol = "#")private String bankCard;private String username;}@Testpublic void testDesensitizeWithInlineVO() throws JsonProcessingException {// 创建测试数据UserVO user = UserVO.builder().id(1L).roleId(101L).idCard("110105199003075678").name("欧阳锋").phoneNumber("13812345678").email("ouyangfeng@shaguyin.com").bankCard("6228480031567890123").username("ofeng").build();// 创建 ObjectMapper(确保注册了自定义序列化器)ObjectMapper mapper = new ObjectMapper();String json = mapper.writeValueAsString(user);System.out.println("序列化结果:");System.out.println(json);// 断言脱敏效果assertThat(json).contains("\"idCard\":\"1101****5678\"");assertThat(json).contains("\"name\":\"欧**\"");assertThat(json).contains("\"phoneNumber\":\"138****5678\"");assertThat(json).contains("\"email\":\"o****@shaguyin.com\"");assertThat(json).contains("\"bankCard\":\"62###############23\"");assertThat(json).contains("\"username\":\"ofeng\"");}
}

在这里插入图片描述

三、效果展示

在这里插入图片描述

import com.fc.anno.Desensitize;
import com.fc.enums.DesensitizeType;
import lombok.Builder;
import lombok.Data;import java.time.LocalDateTime;@Data
@Builder
public class UserVO {private Long id;private Long roleId;@Desensitize(type = DesensitizeType.ID_CARD)private String idCard;private String username;private String name;private String phoneNumber;private LocalDateTime createTime;private LocalDateTime lastLogin;
}

在这里插入图片描述

总结

本文实现了一套基于 Jackson 序列化 + 注解 + 策略模式 的轻量级数据脱敏框架,具备以下优点:
✅ 无侵入性:仅需在 VO 字段添加注解
✅ 高性能:使用 EnumMap + 预编译正则
✅ 易扩展:新增类型只需添加枚举和策略
✅ 灵活配置:支持自定义前后缀与掩码符号
✅ 无缝集成:适用于 Spring Boot + Jackson 项目

http://www.lryc.cn/news/609300.html

相关文章:

  • C语言基础03——数组——习题
  • GPIO交换矩阵和IO_MUX
  • 自动驾驶控制算法——LQR控制算法
  • 直流无刷电机(一)
  • C++ <type_traits> 应用详解
  • 10.Redis 数据类型
  • Maximum Subarray Sum II
  • 【超分辨率专题】PiSA-SR:单步Diff超分新突破,即快又好,还能在线调参
  • 【前端:Html】--1.2.基础语法
  • LCL滤波器及其电容电流前馈有源阻尼设计软件【LCLAD_designer】
  • MCU中的复位生成器(Reset Generator)是什么?
  • 2025年EAAI SCI1区TOP,森林救援调度与路径规划:一种新型蚁群优化算法应用,深度解析+性能实测
  • 【Spring】Bean的作用域(单例、多例、请求、会话、Application)
  • ICCV 2025 | EPD-Solver:西湖大学发布并行加速扩散采样算法
  • Azure DevOps — Kubernetes 上的自托管代理 — 第3部分
  • Autoswagger:揭露隐藏 API 授权缺陷的开源工具
  • Stream 过滤后修改元素,却意外修改原列表
  • 人工智能之数学基础:几何型(连续型)随机事件概率
  • Java开发中敏感信息加密存储全解析:筑牢数据安全防线
  • SpringBoot之整合MyBatisPlus
  • linux火焰图
  • javaweb开发之Servlet笔记
  • 大模型中的Token和Tokenizer:核心概念解析
  • 业务系统跳转Nacos免登录方案实践
  • 电力电子技术知识总结-----PWM知识点
  • 【MybatisPlus】join关联查询MPJLambdaWrapper
  • Javaweb————Windows11系统和idea2023旗舰版手动配置Tomcat9全流程解析
  • 性能测试工具ApacheBench、Jmeter
  • ospf笔记和 综合实验册
  • 在Ansys Mechanical中对磨损进行建模