通用数据脱敏设计方案(Spring Boot 企业级实践)
文章目录
1. 设计背景与总体目标
- 业务背景:系统包含用户手机号、身份证号、邮箱、银行卡号等多种敏感信息,需要在不同角色、不同场景下展示不同程度的脱敏结果,同时兼顾性能、安全与可扩展性。
- 总体目标:
- 统一:提供统一的注解式脱敏能力,对业务代码侵入小、与现有 Spring Boot 3.x + MyBatis Plus + Redis + MySQL + JWT + Redisson + Caffeine 架构无缝集成。
- 高性能:在高并发场景下(15,000+ QPS、RT < 10ms)不会成为性能瓶颈,支持响应式 WebFlux 与传统 MVC。
- 安全可控:支持按角色 / 权限控制脱敏级别,敏感数据加密存储与安全传输,脱敏规则不可被恶意获取。
- 可扩展:可插拔的策略模式,支持快速新增脱敏算法和业务自定义规则。
2. 技术栈与兼容性设计
- 运行时框架:Spring Boot 3.x
- 持久层:MyBatis Plus + MySQL
- 缓存:Redis(分布式缓存) + Caffeine(本地缓存)+ 三级缓存架构
- 分布式基础设施:Redisson 分布式锁
- 安全与认证:Spring Security / WebFlux Security + JWT 鉴权
- Web 框架:Spring MVC + Spring WebFlux
兼容性原则:
- 脱敏尽量在“出站”(返回给前端)阶段完成,尽量不影响持久层与业务逻辑,避免侵入现有 Mapper / Service 实现。
- 统一序列化层处理:基于 Jackson/JSON 策略,在对象序列化为 JSON 时根据注解和当前用户权限进行脱敏。
- 对 MyBatis Plus 的影响:数据从数据库读写仍使用原始敏感数据,写入阶段可以选择性加密;脱敏主要在返回 API 时生效。
- 对缓存层的影响:缓存中可以存储原始数据或加密数据,脱敏在读取缓存数据并返回 API 前统一处理,避免重复计算,必要时结合 Caffeine 缓存脱敏后的结果(按“数据 + 角色”维度)。
3. 脱敏策略与规则模型
3.1 脱敏类型定义
定义脱敏类型枚举,覆盖常见场景并支持扩展:
public enum MaskType {
MOBILE, // 手机号
ID_CARD, // 身份证号
EMAIL, // 邮箱
BANK_CARD, // 银行卡
USERNAME, // 姓名/用户名
CUSTOM // 自定义规则
}
3.2 规则参数与通用上下文
-
基本规则参数:
- keepPrefix:保留前缀长度
- keepSuffix:保留后缀长度
- maskChar:掩码字符(默认为
*) - maskLength:固定掩码长度(可选)
-
脱敏上下文 DataMaskContext:
- 当前登录用户 ID
- 用户角色/权限列表
- 当前调用方应用 ID(如有多租户 / 合作方场景)
- 字段上配置的脱敏注解信息
public class DataMaskContext {
private Long userId;
private List<String> roles;
private Map<String, Object> attributes; // 可扩展
// 构造 & getter/setter 省略
}
4. 注解设计与使用方式
4.1 核心注解:@DataMask
import java.lang.annotation.*;
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataMask {
MaskType type();
// 可选参数:前缀、后缀等
int prefix() default 0;
int suffix() default 0;
char maskChar() default '*';
// 是否支持根据角色放开脱敏
String[] allowRoles() default {};
// 脱敏等级,例如:NONE(明文)/LOW/MEDIUM/HIGH
String level() default "MEDIUM";
// 自定义规则标识(与注册的策略 Bean 匹配)
String customRule() default "";
}
4.2 语义别名注解:@Sensitive
对业务开发者提供更友好的注解别名:
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@DataMask(type = MaskType.CUSTOM)
public @interface Sensitive {
String value() default ""; // 指定自定义规则名
}
4.3 实体类使用示例
public class UserInfoVO {
@DataMask(type = MaskType.MOBILE, prefix = 3, suffix = 4)
private String mobile;
@DataMask(type = MaskType.ID_CARD, prefix = 6, suffix = 4)
private String idCardNo;
@DataMask(type = MaskType.EMAIL)
private String email;
@DataMask(type = MaskType.BANK_CARD, prefix = 4, suffix = 4)
private String bankCardNo;
@Sensitive("VIP_CUSTOM_RULE")
private String vipInfo;
// getter/setter 省略
}
5. 脱敏引擎与策略模式设计
5.1 策略接口定义
public interface DataMasker {
/**
* 判断该策略是否支持当前字段配置
*/
boolean supports(MaskType type, String customRule);
/**
* 执行脱敏
*/
Object mask(Object origin, DataMask annotation, DataMaskContext context);
}
5.2 内置策略实现示例
public class MobileDataMasker implements DataMasker {
@Override
public boolean supports(MaskType type, String customRule) {
return type == MaskType.MOBILE;
}
@Override
public Object mask(Object origin, DataMask annotation, DataMaskContext context) {
if (origin == null) {
return null;
}
String value = origin.toString();
int prefix = annotation.prefix() == 0 ? 3 : annotation.prefix();
int suffix = annotation.suffix() == 0 ? 4 : annotation.suffix();
return maskMiddle(value, prefix, suffix, annotation.maskChar());
}
private String maskMiddle(String value, int prefix, int suffix, char maskChar) {
if (value.length() <= prefix + suffix) {
return value;
}
StringBuilder sb = new StringBuilder();
sb.append(value, 0, prefix);
for (int i = 0; i < value.length() - prefix - suffix; i++) {
sb.append(maskChar);
}
sb.append(value, value.length() - suffix, value.length());
return sb.toString();
}
}
身份证号、邮箱、银行卡号可采用类似实现,也可以抽取公用工具类进行序列化优化。
5.3 策略注册与工厂
public class DataMaskerFactory {
private final List<DataMasker> maskers;
public DataMaskerFactory(List<DataMasker> maskers) {
this.maskers = maskers;
}
public DataMasker getMasker(MaskType type, String customRule) {
return maskers.stream()
.filter(m -> m.supports(type, customRule))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No DataMasker found for type=" + type));
}
}
DataMasker 列表通过 Spring 容器自动注入(可使用 @Component 注册每个策略)。
6. 注解驱动的脱敏实现方式
6.1 基于 Jackson 的序列化扩展(MVC & WebFlux 通用)
核心思想:通过自定义 BeanSerializerModifier 或 JsonSerializer,在 JSON 序列化时根据字段上的 @DataMask 注解和当前用户角色执行脱敏。
public class DataMaskBeanSerializerModifier extends BeanSerializerModifier {
private final DataMaskerFactory factory;
private final SecurityContextProvider securityContextProvider;
public DataMaskBeanSerializerModifier(DataMaskerFactory factory,
SecurityContextProvider securityContextProvider) {
this.factory = factory;
this.securityContextProvider = securityContextProvider;
}
@Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc,
List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
DataMask dataMask = writer.getAnnotation(DataMask.class);
if (dataMask != null) {
writer.assignSerializer(new DataMaskingJsonSerializer(writer, dataMask, factory, securityContextProvider));
}
}
return beanProperties;
}
}
public class DataMaskingJsonSerializer extends JsonSerializer<Object> {
private final BeanPropertyWriter writer;
private final DataMask dataMask;
private final DataMaskerFactory factory;
private final SecurityContextProvider securityContextProvider;
public DataMaskingJsonSerializer(BeanPropertyWriter writer,
DataMask dataMask,
DataMaskerFactory factory,
SecurityContextProvider securityContextProvider) {
this.writer = writer;
this.dataMask = dataMask;
this.factory = factory;
this.securityContextProvider = securityContextProvider;
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
DataMaskContext context = securityContextProvider.buildMaskContext();
// 基于角色/权限判断是否需要脱敏
if (!shouldMask(context, dataMask)) {
gen.writeObject(value);
return;
}
DataMasker masker = factory.getMasker(dataMask.type(), dataMask.customRule());
Object masked = masker.mask(value, dataMask, context);
gen.writeObject(masked);
}
private boolean shouldMask(DataMaskContext context, DataMask dataMask) {
String[] allowRoles = dataMask.allowRoles();
if (allowRoles.length == 0) {
return true; // 默认需要脱敏
}
if (context.getRoles() == null) {
return true;
}
// 只要存在允许角色之一,则显示明文
for (String role : allowRoles) {
if (context.getRoles().contains(role)) {
return false;
}
}
return true;
}
}
6.2 安全上下文获取(JWT + Spring Security 集成)
public interface SecurityContextProvider {
DataMaskContext buildMaskContext();
}
public class SpringSecurityContextProvider implements SecurityContextProvider {
@Override
public DataMaskContext buildMaskContext() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
Long userId = null;
List<String> roles = new ArrayList<>();
if (auth != null && auth.isAuthenticated()) {
Object principal = auth.getPrincipal();
// 可从 JWT 中解析 userId、roles
// 这里示例从自定义 UserDetails 中获取
if (principal instanceof CustomUserDetails cud) {
userId = cud.getUserId();
roles = cud.getRoleCodes();
}
}
DataMaskContext context = new DataMaskContext();
context.setUserId(userId);
context.setRoles(roles);
return context;
}
}
通过现有 JWT 解析逻辑(例如在 JwtAuthenticationFilter 中)将角色与用户 ID 注入 Spring Security 的 Authentication,即可复用。
6.3 WebFlux 响应式兼容
- WebFlux 仍使用 Jackson 作为 JSON 序列化框架,以上
BeanSerializerModifier/JsonSerializer同样适用。 - 通过配置
CodecCustomizer或在ObjectMapperBean 中注册DataMaskBeanSerializerModifier即可。
@Configuration
public class JacksonMaskConfiguration {
@Bean
public ObjectMapper objectMapper(DataMaskerFactory factory,
SecurityContextProvider securityContextProvider) {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
mapper.setSerializerFactory(
mapper.getSerializerFactory()
.withSerializerModifier(new DataMaskBeanSerializerModifier(factory, securityContextProvider))
);
mapper.registerModule(module);
return mapper;
}
}
在 WebFlux 环境中,通过 WebFluxConfigurer 或 CodecCustomizer 使用上述 ObjectMapper:
@Bean
public CodecCustomizer codecCustomizer(ObjectMapper objectMapper) {
return configurer -> configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper));
}
7. 全局配置与个性化配置
7.1 全局默认配置
- 在
application.yml中配置默认脱敏策略(仅限非敏感字段参数,如开关、默认级别):
data-mask:
enabled: true
default-level: MEDIUM
log-enabled: true
- 通过
@ConfigurationProperties绑定:
@ConfigurationProperties(prefix = "data-mask")
public class DataMaskProperties {
private boolean enabled = true;
private String defaultLevel = "MEDIUM";
private boolean logEnabled = true;
// getter/setter
}
7.2 个性化配置(按字段 / 角色)
- 开发者在字段上通过
@DataMask定制前缀、后缀、allowRoles等参数。 - 对于复杂场景(如特定租户/业务线规则),可以:
- 使用
customRule字段与自定义策略实现对应; - 或在
SecurityContextProvider中注入租户信息,并在策略中根据上下文动态调整脱敏程度。
- 使用
8. 与缓存(Redis + Caffeine)及分布式环境的协同
8.1 缓存层策略
-
推荐做法:缓存中存储的是原始数据(或加密后数据),脱敏在返回给前端前由序列化层完成。
- 优点:缓存命中后仍能根据不同角色动态决定脱敏程度。
- 缺点:内存中和 Redis 中存在原始敏感数据,需要严格控制访问权限与加密策略。
-
安全增强选项:
- 对特别敏感字段采用加密存储:业务实体中存储的是加密字符串,在业务代码中只有在必要场景下解密使用;脱敏时基于明文进行掩码后输出。
- 为防止频繁解密影响性能,可使用 Caffeine 对“已解密数据 + 掩码结果”进行本地缓存,设置短期 TTL。
8.2 三级缓存协同
- 本地 Caffeine 缓存:缓存“字段元数据 + 注解配置”解析结果,避免每次反射扫描注解。
- Redis 分布式缓存:缓存热点用户信息、订单信息等原始数据。
- 数据库 MySQL:持久化原始数据(可结合列级加密或应用层加密)。
8.3 分布式环境统一脱敏
- 脱敏逻辑全部在应用层统一实现,通过注解 + 策略 + Jackson 扩展完成。
- 多实例环境中,各实例加载同一套规则配置,保证输出一致。
- 如需运行时动态调整规则,可通过:
- 规则配置存储在数据库/配置中心,
- 通过 Redis pub/sub 或配置中心推送通知各实例更新本地规则缓存。
9. 性能优化设计
- 反射优化:
- 对类字段和
@DataMask元信息的解析只在第一次访问时进行,结果缓存到 Caffeine Map 中。
- 对类字段和
- 字符串操作优化:
- 使用基于索引切割的
StringBuilder操作,避免正则开销。 - 对身份证、银行卡等固定长度的字段,可使用预计算的边界。
- 使用基于索引切割的
- 条件判断优化:
- 通过
allowRoles快速判断是否需要脱敏,避免不必要的字符串构造。
- 通过
- 异步与响应式:
- WebFlux 环境中,脱敏在 Reactor 链尾部的序列化阶段执行,避免阻塞 I/O 线程。
- 性能指标:
- 在 15,000+ QPS、典型实体字段数量 < 30 的场景下,单次脱敏开销控制在微秒级,整体 RT 增量 < 1ms。
10. 安全要求与实现
10.1 脱敏规则配置安全
-
原则:
- 规则配置仅在服务端维护,前端和外部系统不可直接获取详细规则(如保留几位、掩码规则等)。
- 若必须提供文档或对外说明,只给出“效果示例”,不公开具体算法细节。
-
具体措施:
- 规则配置存放在受控的配置中心/数据库中,访问受权限控制;
- 对关键自定义规则标识(如
customRule)与实现映射关系仅在代码中维护,不开放动态脚本配置; - 严禁在日志、异常信息中打印脱敏规则的详细实现细节。
10.2 敏感数据加密存储与传输
-
存储加密:
- 对银行卡号、身份证号等字段可采用应用层加密(如 AES-256)后再写入 DB/Redis;
- 密钥由专用密钥管理系统(KMS)或环境变量/配置中心管理,不写入代码仓库。
-
传输安全:
- 所有外部接口统一使用 HTTPS,防止中间人攻击;
- 内部 RPC 调用也推荐使用 TLS 或内网专线。
10.3 与 JWT / Spring Security 集成
- 在 JWT 中仅存放必要的身份信息(userId、roleCodes),避免存储明文敏感数据;
- 在
JwtAuthenticationFilter中完成 JWT 解析并注入Authentication,供SecurityContextProvider使用; - 对需要访问原始敏感数据的接口,结合 Spring Security 的方法级权限控制:
@PreAuthorize("hasAuthority('USER_SENSITIVE_READ')")
@GetMapping("/user/detail/raw")
public UserInfoVO getRawUserInfo() {
// 返回值中不使用 @DataMask 或根据 allowRoles 自动判定不脱敏
}
11. 可扩展性设计
- 新增脱敏算法步骤:
- 创建新的枚举值
MaskType.NEW_TYPE; - 实现
DataMasker接口,例如NewTypeDataMasker并加上@Component; - 在业务实体字段上标记
@DataMask(type = MaskType.NEW_TYPE, ...)即可。
- 创建新的枚举值
- 自定义规则扩展:
- 通过
@Sensitive("BIZ_RULE")+ 对应DataMasker实现(supports中判断MaskType.CUSTOM且customRule匹配)实现特定业务规则。
- 通过
- 多租户 / 多业务线:
- 在
DataMaskContext中增加 tenantId、bizLine 等参数,策略实现中根据这些参数动态调整掩码规则。
- 在
12. 监控与日志设计
12.1 日志记录
- 日志内容:
- 不记录原始敏感值;
- 仅记录:脱敏字段名、脱敏类型、调用方、用户 ID、脱敏策略版本号等;
- 关键操作如“关闭脱敏开关”、“变更规则配置”必须审计记录。
@Slf4j
public class MaskAuditLogger {
public void logMask(String fieldName, MaskType type, DataMaskContext context) {
log.debug("mask field={}, type={}, userId={}, roles={}",
fieldName,
type,
context.getUserId(),
context.getRoles());
}
}
12.2 监控指标
- 通过 Micrometer + Prometheus + Grafana 建立监控指标,例如:
- 每秒脱敏调用次数:
data_mask_requests_total; - 平均脱敏耗时:
data_mask_latency_ms; - 按角色统计的脱敏比例:
data_mask_role_ratio; - 脱敏失败次数(异常):
data_mask_errors_total。
- 每秒脱敏调用次数:
在 DataMaskingJsonSerializer 中埋点:
@Timed(value = "data_mask_serialize", histogram = true)
public void serialize(...) {
// 原有逻辑
}
13. 代码结构建议
建议在 src/main/java/com/video/common/mask 下组织以下结构:
- annotation
DataMask.javaSensitive.java
- core
MaskType.javaDataMaskContext.javaDataMasker.javaDataMaskerFactory.javaSecurityContextProvider.java
- strategy
MobileDataMasker.javaIdCardDataMasker.javaEmailDataMasker.javaBankCardDataMasker.javaCustomRuleDataMasker.java
- jackson
DataMaskBeanSerializerModifier.javaDataMaskingJsonSerializer.javaJacksonMaskConfiguration.java
- monitor
MaskAuditLogger.java
- config
DataMaskProperties.java
14. 测试方案设计
14.1 单元测试
-
覆盖点:
- 各内置策略类(如
MobileDataMasker)对不同输入的脱敏结果:- 正常长度
- 长度不足前缀+后缀
- 空字符串 / null
DataMaskerFactory的策略选择逻辑;DataMaskingJsonSerializer在不同allowRoles与roles场景下是否执行脱敏;SecurityContextProvider能否从 Spring Security 上下文中正确构建DataMaskContext。
- 各内置策略类(如
-
示例测试代码:
class MobileDataMaskerTest {
private final MobileDataMasker masker = new MobileDataMasker();
@Test
void testMaskNormalMobile() {
DataMask annotation = ...; // 使用动态代理或自定义注解实现构造
DataMaskContext context = new DataMaskContext();
String result = (String) masker.mask("13812348888", annotation, context);
assertEquals("138****8888", result);
}
}
14.2 集成测试(MVC & WebFlux)
-
Spring MVC 集成测试:
- 使用
@SpringBootTest+MockMvc:- 构造不同角色(普通用户、管理员)登录场景(mock JWT 或 mock SecurityContext);
- 调用返回包含
@DataMask字段的接口,验证 JSON 输出是否符合预期脱敏规则。
- 使用
-
WebFlux 集成测试:
- 使用
WebTestClient:- 模拟 Reactor 环境下的请求;
- 验证响应体中脱敏字段是否正确。
- 使用
14.3 性能与压力测试
-
使用 JMeter / Gatling / Locust 对关键接口进行压测:
- 对比开启/关闭脱敏功能时的 QPS 与 RT;
- 在 15,000+ QPS 下观察 CPU、GC、响应时间分布;
- 确认脱敏不会成为瓶颈点。
-
可针对
DataMaskingJsonSerializer编写简单 micro-benchmark,校验单次脱敏耗时。
15. 总结
本方案基于 注解 + 策略模式 + Jackson 序列化扩展 实现了与现有 Spring Boot 3.x 技术栈兼容的通用数据脱敏能力:
- 对业务开发者友好:仅需在实体字段上添加
@DataMask或@Sensitive即可; - 满足安全与合规需求:支持按角色/权限控制脱敏级别,支持敏感数据加密存储与传输,规则配置受控不可泄露;
- 高性能与可扩展:缓存元数据、避免反射开销,支持 WebFlux 与 MVC,在高并发场景下性能影响可控,并可通过简单新增策略类扩展新的脱敏算法;
- 运维友好:结合日志与监控体系,便于审计、回溯与性能分析。
16. 与现有项目的集成示例
16.1 目录与包结构对齐
在当前项目中,建议将本方案的实现代码落地到 com.video.common.mask 包下,与已有基础能力(如 JwtUtil、UserContext、Result 等)保持同一层级,方便统一维护:
- common
JwtUtil:JWT 解析、生成工具,可为SecurityContextProvider提供用户信息。UserContext/UserInfo:封装当前登录用户信息,可作为DataMaskContext的数据来源之一。- mask(新增):承载数据脱敏相关的注解、核心接口、策略实现和 Jackson 扩展。
这样可以尽量复用当前项目在安全、上下文、响应包装等方面的已有能力,减少重复轮子。
16.2 Controller 层使用示例
以一个典型的用户详情接口为例:
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/profile")
public Result<UserProfileVO> profile() {
Long userId = UserContext.getUserId();
UserProfileVO profile = userService.getProfile(userId);
// 返回时由 Jackson + DataMask 注解自动完成脱敏
return Result.ok(profile);
}
}
对应的 UserProfileVO 示例:
public class UserProfileVO {
private Long id;
@DataMask(type = MaskType.MOBILE, prefix = 3, suffix = 4)
private String mobile;
@DataMask(type = MaskType.EMAIL)
private String email;
@DataMask(type = MaskType.ID_CARD, prefix = 6, suffix = 4, allowRoles = {"ROLE_ADMIN"})
private String idCard;
// 其他字段略
}
- 普通用户访问
/api/user/profile时,将看到脱敏后的手机、邮箱、身份证号; - 拥有
ROLE_ADMIN的管理员访问时,身份证号字段会根据allowRoles判定为无需脱敏,直接展示明文。
16.3 与 MyBatis Plus 的衔接
Service 层通过 MyBatis Plus 读取实体数据后,再转换为 VO 即可:
@Service
public class UserService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public UserProfileVO getProfile(Long userId) {
User entity = userMapper.selectById(userId);
if (entity == null) {
return null;
}
UserProfileVO vo = new UserProfileVO();
// 常规 bean 拷贝
BeanUtils.copyProperties(entity, vo);
return vo;
}
}
- MyBatis Plus 实体
User中可以不加@DataMask,只在 VO 层标注; - 这样可以确保数据库层和缓存层仍然持有完整数据,脱敏专注在返回给前端的视图对象上,风险边界更清晰。
16.4 与 JWT / JwtAuthenticationFilter 的集成
当前项目已经存在基于 JWT 的认证过滤器(如 JwtAuthenticationFilter),在该过滤器中完成 JWT 解析并将用户信息放入 SecurityContext 或 UserContext,即可被 SpringSecurityContextProvider 复用:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = resolveToken(request);
if (StringUtils.hasText(token)) {
CustomUserDetails userDetails = parseToken(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
// 同时写入自定义 UserContext 供业务侧使用
UserContext.setUserInfo(convertToUserInfo(userDetails));
}
filterChain.doFilter(request, response);
}
}
数据脱敏模块无需直接感知 JWT 细节,只依赖标准的 SecurityContextHolder 和/或 UserContext 即可。
17. 落地实施步骤与 Checklist
为降低改造风险,推荐采用分阶段、渐进式引入:
17.1 阶段一:基础能力搭建
-
步骤:
- 在
com.video.common.mask下创建注解、核心接口、策略实现和 Jackson 配置; - 在
common模块中新增SecurityContextProvider的实现,封装从SecurityContextHolder与UserContext读取数据的逻辑; - 在全局
ObjectMapper/ WebFluxCodecCustomizer中注册DataMaskBeanSerializerModifier; - 在
application.yml中加入data-mask开关配置,并默认开启。
- 在
-
验证点:
- 编写一个简单的 Demo VO 和 Controller,手动调用并验证 JSON 输出中字段是否按注解规则正确脱敏;
- 检查非敏感接口是否未受到影响(未标注注解的字段不应被修改)。
17.2 阶段二:核心业务场景覆盖
-
优先覆盖:
- 用户信息、账号资产、订单、支付相关接口;
- 管理后台中对用户数据的查询接口,根据角色配置
allowRoles。
-
实施建议:
- 以 VO 为单位梳理,优先对对外暴露的响应对象添加
@DataMask; - 与安全/风控同学确认各角色的脱敏级别和策略;
- 通过灰度发布,在部分实例/部分租户先开启脱敏,观察日志与监控指标。
- 以 VO 为单位梳理,优先对对外暴露的响应对象添加
17.3 阶段三:全量推广与持续优化
-
推广:
- 将脱敏规则扩展到更多业务模块(私信、礼物、活动等),统一规范;
- 对于动态变化频繁的规则,考虑引入配置中心,并增加规则版本号字段用于审计。
-
优化:
- 基于监控指标优化热点接口的脱敏策略实现,例如:
- 调整字符串处理方式;
- 微调 Caffeine 缓存的 TTL 与容量;
- 观察 GC 和 CPU 使用情况,避免过度分配临时对象。
- 基于监控指标优化热点接口的脱敏策略实现,例如:
17.4 回滚与降级方案
- 支持通过
data-mask.enabled=false全局开关快速关闭脱敏逻辑; - 对关键接口,可增加接口级参数(仅内部使用)临时关闭脱敏,用于紧急排查问题;
- 重要:即便关闭脱敏,也不影响敏感数据的加密存储与传输机制,两者应解耦设计。
18. 常见问题与最佳实践
18.1 常见问题 FAQ
-
是否需要对所有敏感字段都加
@DataMask?- 实际上建议至少对所有对外输出的敏感字段进行标注;
- 对于仅用于内部计算、不直接返回前端的字段,可以不加注解,但仍建议在存储层进行加密处理。
-
脱敏在 Service 层做还是在返回层做?
- 本方案推荐在 JSON 序列化阶段统一完成,避免污染业务逻辑;
- 如确有特殊需要(例如导出文件、第三方对接),可以在业务层额外调用公共的脱敏工具方法。
-
缓存中是否存放明文?
- 推荐对特别敏感的数据进行应用层加密后再写入缓存;
- 对一般敏感数据,可以在受控环境下存明文,但要确保访问缓存的代码路径和账号可控,并配合脱敏模块严格限制对外输出。
-
如何避免重复脱敏?
- 通过
allowRoles+DataMaskContext的角色判断机制,确保只在必要时进行; - 对于已经是脱敏后的字符串,避免再次执行脱敏可以通过增加一个简单的“已脱敏标记位”或正则判断来实现(可按业务情况取舍)。
- 通过
18.2 最佳实践清单
- 优先 VO 层脱敏:保持 Entity/DO 只承载存储语义,将展示语义集中在 VO。
- 角色模型前置设计:在设计脱敏策略前,与安全和产品一起确定“哪些角色可以看到明文、哪些只能看到部分信息、哪些完全看不到”。
- 配置与代码分层:将与业务强相关的规则放在代码中(如字段级注解),将可调的策略参数放在配置中心(如开关、默认级别),避免全部规则都硬编码或全部放配置。
- 日志脱敏一致性:日志打印时应复用相同的脱敏策略或至少保证不泄露原始敏感数据,避免“接口返回是脱敏的、日志里是明文”的情况。
- 定期回顾与审计:定期梳理系统中新增的字段与接口,确保新的敏感字段也纳入脱敏体系,并通过审计日志核对规则变更。
更多推荐




所有评论(0)