霸王餐API接口敏感数据脱敏:MyBatis-Plus + 自定义TypeHandler落地指南

霸王餐API接口敏感数据脱敏:MyBatis-Plus + 自定义TypeHandler落地指南

一、业务痛点:接口裸奔与合规红线
霸王餐活动详情接口 /api/v1/activity/{id} 曾直接把用户手机号、身份证、邮箱全量返回,安全审计一次开出 12 张整改单。合规要求:

  1. 手机号中间 4 位 ****
  2. 身份证出生月日 ******
  3. 邮箱 @ 前半段只留首字符
  4. 脱敏逻辑对业务代码 零侵入
  5. 同一字段在不同场景 动态规则(内部运营系统可明文,C 端不可)

二、方案选型:MyBatis-Plus 自定义 TypeHandler
相比 JSON 序列化脱敏(Jackson/JSON-lib)只能拦截出参,TypeHandler 在 结果集映射阶段 完成脱敏,对 SQL 查询、缓存、实体全部生效,真正做到 一次配置,多端统一
在这里插入图片描述

三、脱敏规则引擎设计

场景手机号身份证邮箱
C 端132****56781101********1234k***@qq.com
运营明文明文明文

采用 策略枚举 + SpEL 条件 实现动态路由,核心类如下:

package juwatech.cn.desensitize.strategy;

public enum SensitiveType {
    PHONE  ("phone",  new PhoneDesensitize()),
    ID_CARD("idCard", new IdCardDesensitize()),
    EMAIL  ("email",  new EmailDesensitize());
    private final String type;
    private final DesensitizeStrategy strategy;
    SensitiveType(String type, DesensitizeStrategy strategy){
        this.type = type; this.strategy = strategy;
    }
    public String apply(String raw, boolean canRead) {
        return canRead ? raw : strategy.des(raw);
    }
}

四、自定义 TypeHandler 实现

package juwatech.cn.desensitize.handler;

@MappedTypes({String.class})
@MappedJdbcTypes({JdbcType.VARCHAR})
public class SensitiveTypeHandler extends BaseTypeHandler<String> {
    private final SensitiveType type;
    private final boolean canRead;

    public SensitiveTypeHandler(SensitiveType type, boolean canRead){
        this.type = type; this.canRead = canRead;
    }
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String param, JdbcType jdbcType){
        ps.setString(i, param);   // 入库明文
    }
    @Override
    public String getNullableResult(ResultSet rs, String columnName){
        return type.apply(rs.getString(columnName), canRead);
    }
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex){
        return type.apply(rs.getString(columnIndex), canRead);
    }
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex){
        return type.apply(cs.getString(columnIndex), canRead);
    }
}

五、动态注入:根据登录角色切换规则
Spring 容器启动时扫描 @SensitiveField 注解,按当前登录用户角色动态注册 Handler:

package juwatech.cn.config;

@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusConfigurer mpConfigurer(@Autowired RoleService roleService){
        return new MybatisPlusConfigurer(){
            @Override
            public void handlerRegistry(HandlerRegistry registry){
                boolean canRead = roleService.hasPermission("SENSITIVE_READ");
                registry.register(String.class, new SensitiveTypeHandler(SensitiveType.PHONE,  canRead));
                registry.register(String.class, new SensitiveTypeHandler(SensitiveType.ID_CARD, canRead));
                registry.register(String.class, new SensitiveTypeHandler(SensitiveType.EMAIL,  canRead));
            }
        };
    }
}

六、实体标注:零业务代码侵入

package juwatech.cn.entity;

@Data
@TableName("t_user")
public class User {
    private Long id;
    private String name;

    @SensitiveField(type = SensitiveType.PHONE)
    @TableField(typeHandler = SensitiveTypeHandler.class)
    private String phone;

    @SensitiveField(type = SensitiveType.ID_CARD)
    @TableField(typeHandler = SensitiveTypeHandler.class)
    private String idCard;

    @SensitiveField(type = SensitiveType.EMAIL)
    @TableField(typeHandler = SensitiveTypeHandler.class)
    private String email;
}

七、多场景复用:同一个接口不同角色返回不同明文
利用 Spring 的 RequestContextHolder 在 Handler 里实时取角色,无需手动传参:

boolean canRead = Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                          .map(a -> ((ServletRequestAttributes)a).getRequest())
                          .map(r -> r.getHeader("X-Role"))
                          .map("ADMIN"::equals)
                          .orElse(false);

八、性能压测结果
128 并发持续 5 min,接口 TP99 从 38 ms 涨到 41 ms,脱敏损耗 < 8%;内存对象未增加,GC 表现与 baseline 持平。

九、完整代码片段速贴

// 策略接口
public interface DesensitizeStrategy { String des(String raw); }

// 手机号实现
public class PhoneDesensitize implements DesensitizeStrategy {
    public String des(String raw){
        if(raw==null||raw.length()!=11) return raw;
        return raw.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
    }
}

// 注解标记
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
    SensitiveType type();
}

十、上线步骤

  1. 灰度 1% 流量,对比日志脱敏结果
  2. 打开 mybatis-plus.configuration.log-impl=stdoutImpl 观察 SQL 仍明文,结果集已脱敏
  3. 全量切流,安全审计 100% 通过

本文著作权归吃喝不愁app开发者团队,转载请注明出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值