记一次隐私数据加解密失效的问题排查

欢迎关注公众号:【冬瓜白】

问题初现:手机号查询突然失效

前天测试同学突然找上门来:"你们项目有个问题卡了好几天,用手机号查不到订单了!"我心想这不应该啊,手机号查询可是基础功能。但仔细一看,这问题居然还真阻塞测试好几天了,看来不是简单的SQL写错那么简单。

我们的系统对手机号这类隐私数据有特殊处理:

  • 数据库存两个字段:tel(不可逆加密)和tel_c(可逆加密)
  • 查询时通过MyBatis拦截器自动处理加解密

排查过程

刚开始我怀疑是SQL写错了,但很快排除了这个可能。于是把目光转向了加解密拦截器——这个平时默默工作的"幕后英雄"。

这个拦截器的核心逻辑其实很清晰:

  1. 解析SQL参数,判断哪些字段需要加密
  2. 对需要加密的字段进行处理
  3. 执行SQL并处理返回结果

问题很可能出在第一步的参数解析上。这里用到了MyBatis的boundSql.getParameterObject()方法,它根据参数类型返回不同结果:

  • 单个参数:直接返回
  • 多个参数:返回MapperMethod.ParamMap
  • 对象参数:返回对象本身

对比修改前后的Mapper接口:

修改前

List<SrvInfo> searchSrvList(
    @Param("searchVO") SrvSearchVo searchVo,
    @Param("start") int start,
    @Param("pageSize") int pageSize,
    @Param("maxNum") Integer maxNum);

修改后

List<SrvInfo> searchSrvList(
    @Param("searchVO") SrvSearchVo searchVo,
    @Param("start") int start,
    @Param("pageSize") int pageSize,
    @Param("maxNum") Integer maxNum,
    @Param("isOversea") boolean isOversea); // 新增参数

就是这新增的isOversea参数搞的鬼!拦截器里用了个"偷懒"的实现:

Object obj = ((MapperMethod.ParamMap) parameterObj).values().iterator().next();

直接取了ParamMap的第一个值来判断是否需要加密。

HashMap 的顺序不可靠

问题在于MapperMethod.ParamMap继承自HashMap,而HashMap的迭代顺序是不保证的!新增一个参数可能导致原有参数的顺序改变。

写个简单测试就一目了然:

public static void main(String[] args) {
        System.out.println("修改前的 MapperMethod.ParamMap:");
        Map<String, Object> map2 = new HashMap<>();

        map2.put("searchVO", "searchVO");
        map2.put("start", "start");
        map2.put("pageSize", "pageSize");
        map2.put("param3", "param3");
        map2.put("maxNum", "maxNum");
        map2.put("param4", "param4");
        map2.put("param1", "param1");
        map2.put("param2", "param2");

        // 使用迭代器遍历 map2
        Iterator<Map.Entry<String, Object>> iterator2 = map2.entrySet().iterator();
        while (iterator2.hasNext()) {
            Map.Entry<String, Object> entry = iterator2.next();
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }

        System.out.println("================");

        System.out.println("修改后的 MapperMethod.ParamMap:");
        Map<String, Object> map1 = new HashMap<>();

        // 将 Key 和 Value 设置为相同的值
        map1.put("param5", "param5");
        map1.put("isOversea", "isOversea");
        map1.put("searchVO", "searchVO");
        map1.put("start", "start");
        map1.put("pageSize", "pageSize");
        map1.put("param3", "param3");
        map1.put("maxNum", "maxNum");
        map1.put("param4", "param4");
        map1.put("param1", "param1");
        map1.put("param2", "param2");

        Iterator<Map.Entry<String, Object>> iterator = map1.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> entry = iterator.next();
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
    }

输出:

修改前的 MapperMethod.ParamMap:
searchVO -> searchVO
start -> start
pageSize -> pageSize
param3 -> param3
maxNum -> maxNum
param4 -> param4
param1 -> param1
param2 -> param2
================
修改后的 MapperMethod.ParamMap:
param5 -> param5
isOversea -> isOversea
searchVO -> searchVO
start -> start
pageSize -> pageSize
param3 -> param3
maxNum -> maxNum
param4 -> param4
param1 -> param1
param2 -> param2

解决方案

原来的实现太依赖HashMap的顺序了,这明显是个坑。改进方案也很简单,不取巧,直接全量遍历 MapperMethod.ParamMap 即可。

总结

不要依赖HashMap的顺序:这是老生常谈,但实际开发中还是容易被忽略而踩坑。很多工具用起来简单,但想用好必须了解其底层机制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值