前言
个人敏感信息保护已成为合规与品牌信任的底线需求。数据脱敏并非简单“打星号”, 而是从“产生、传输、存储、展示、日志、审计”全链路的系统化工程。本文给出一套在 SpringBoot 中可落地的通用方案, 兼顾可配置性、性能与安全边界。
一、敏感数据分级与合规约束
- P0 核心敏感: 身份证号、银行卡号、密码、密钥、私密地址
- P1 高敏: 手机号、邮箱、姓名、住址、IP、设备号
- P2 业务敏感: 订单号、用户ID、内部单号
分级决定脱敏强度、记录审计策略与访问控制边界, 并影响是否需要加密存储与密钥轮换。
二、全链路脱敏范围与策略
- 接口入参校验与清洗
- 应用层返回值序列化脱敏
- 持久化读写层: ORM/Mapper 拦截
- 日志打印与埋点脱敏
- 导出报表与消息队列载荷脱敏
三、注解 + AOP 执行时脱敏设计
核心思路: 使用注解标注敏感字段或方法返回值, 由 AOP 统一处理。支持按“规则类型 + 掩码参数”实现可配置。
3.1 注解定义
1 2 3 4 5 6 7 8 9 10 11
| @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) public @interface Sensitive { SensitiveRule rule(); int prefix() default 0; int suffix() default 0; }
public enum SensitiveRule { MOBILE, ID_CARD, BANK_CARD, EMAIL, NAME, ADDRESS, CUSTOM }
|
3.2 规则与工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public final class MaskingUtils { private MaskingUtils() {}
public static String maskMobile(String s) { if (s == null || s.length() < 7) return s; return s.substring(0,3) + "****" + s.substring(s.length()-4); }
public static String maskEmail(String s) { if (s == null) return null; int i = s.indexOf('@'); if (i <= 1) return "*" + s.substring(i); return s.charAt(0) + "****" + s.substring(i); } }
|
3.3 切面处理返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Aspect @Component public class SensitiveAspect { @Around("execution(* com.example..controller..*(..))") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object ret = pjp.proceed(); return maskObject(ret); }
private Object maskObject(Object obj) { if (obj == null) return null; if (obj instanceof Collection<?> col) { col.forEach(this::maskObject); return obj; } if (obj.getClass().getPackageName().startsWith("java.")) return obj; for (Field f : obj.getClass().getDeclaredFields()) { Sensitive ann = f.getAnnotation(Sensitive.class); if (ann == null) continue; f.setAccessible(true); Object v = ReflectionUtils.getField(f, obj); if (v instanceof String s) { String masked = applyRule(ann, s); ReflectionUtils.setField(f, obj, masked); } } return obj; }
private String applyRule(Sensitive ann, String s) { return switch (ann.rule()) { case MOBILE -> MaskingUtils.maskMobile(s); case EMAIL -> MaskingUtils.maskEmail(s); default -> genericMask(s, ann.prefix(), ann.suffix()); }; }
private String genericMask(String s, int prefix, int suffix) { if (s == null) return null; int len = s.length(); int p = Math.min(prefix, len); int q = Math.min(suffix, Math.max(0, len - p)); int stars = Math.max(0, len - p - q); return s.substring(0, p) + "*".repeat(stars) + s.substring(len - q); } }
|
适用: 控制器返回 DTO 场景, 快速落地, 与序列化脱敏可组合。
四、序列化层脱敏 Jackson 模块
在 JSON 输出层统一处理, 对标注 @Sensitive 的字段应用 JsonSerializer。优势是与 AOP 解耦, 仅在序列化时生效, 不污染内存对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class SensitiveSerializer extends JsonSerializer<String> { private final SensitiveRule rule; private final int prefix; private final int suffix;
public SensitiveSerializer(SensitiveRule rule, int prefix, int suffix) { this.rule = rule; this.prefix = prefix; this.suffix = suffix; }
@Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { String masked = switch (rule) { case MOBILE -> MaskingUtils.maskMobile(value); case EMAIL -> MaskingUtils.maskEmail(value); default -> genericMask(value, prefix, suffix); }; gen.writeString(masked); }
private String genericMask(String s, int pre, int suf) { if (s == null) return null; int len = s.length(); int p = Math.min(pre, len); int q = Math.min(suf, Math.max(0, len - p)); int stars = Math.max(0, len - p - q); return s.substring(0, p) + "*".repeat(stars) + s.substring(len - q); } }
|
1 2 3 4 5 6 7 8 9
| @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = SensitiveSerializer.class) public @interface JsonSensitive { SensitiveRule rule(); int prefix() default 0; int suffix() default 0; }
|
注册模块:
1 2 3 4 5 6 7 8 9
| @Configuration public class JacksonConfig { @Bean public ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); return mapper; } }
|
五、持久化与查询层脱敏
- 写入前: 对需要密文存储的数据进行加密(如 Jasypt/KMS), 严禁明文入库
- 查询后: 仅在出库面向外部场景进行脱敏展示, 内部闭环计算保留原值
- MyBatis 插件/拦截器: 对
ResultSet 到对象映射阶段注入脱敏逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})}) public class DesensitizeInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object result = invocation.proceed(); if (result instanceof List<?> list) { for (Object row : list) maskByAnnotation(row); } return result; }
private void maskByAnnotation(Object obj) { for (Field f : obj.getClass().getDeclaredFields()) { Sensitive ann = f.getAnnotation(Sensitive.class); if (ann == null) continue; f.setAccessible(true); Object v = ReflectionUtils.getField(f, obj); if (v instanceof String s) { String masked = s.length() > 0 ? "*" + s.substring(Math.max(1, s.length() - 1)) : s; ReflectionUtils.setField(f, obj, masked); } } } }
|
六、日志与审计脱敏
- 禁止将明文敏感信息写入日志
- 使用
MaskingLayout 或 Logback TurboFilter 对日志参数进行脱敏
- Trace/Span 标签仅记录哈希或截断值, 避免可逆还原
示例 Logback 过滤器思路:
1
| <turboFilter class="com.example.log.MaskTurboFilter"/>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MaskTurboFilter extends TurboFilter { @Override public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) { if (params != null) { for (int i = 0; i < params.length; i++) { if (params[i] instanceof String s) params[i] = sanitize(s); } } return FilterReply.NEUTRAL; }
private String sanitize(String s) { return s.replaceAll("(1[3-9]\\d{9})", "***$1***"); } }
|
七、规则引擎与可配置策略
- 规则元数据: 字段名、规则类型、保留位数、场景(接口/日志/导出)
- 热更新: 配置中心推送规则, 通过
AtomicReference/双缓冲生效
- 多场景策略: 接口场景强脱敏, 日志场景严控输出, 内部计算场景可不脱敏
八、性能与稳定性
- 序列化层脱敏优先, AOP 次之, 避免多次遍历
- 热点对象避免反射开销: 缓存字段元信息或使用 ASM/MapStruct
- 压测关注 P99 序列化耗时与 GC 回收压力
九、安全边界与绕过防护
- 黑名单关键字与格式校验, 防止“看似非敏感”绕过
- 对导出/下载等异步任务进行二次检查与最小化字段集
- 与权限系统联动, 非授权主体无原值访问能力
十、实施清单
- 敏感数据分级与清单
- 注解/规则定义与覆盖率评估
- Jackson 序列化脱敏与单元测试
- MyBatis 拦截器/日志过滤器集成
- 性能基线与压测报告
- 审计与回溯机制
- 灰度与回滚预案
十一、FAQ