啊!我只是想复制一下,怎么bug就改不完了呀!

168 阅读3分钟

问题虽然很常见,也在这儿记录分享一下吧

项目简述: 最近我收到个任务是要将我们的答题活动,对接放到微信公众号中去,微信和app能够同时参加活动、抽奖。搬迁之后复测,看了下代码,我滴妈,问题太多了!(代码是21年写的,非常混乱逻辑全在controller的method里面,估计这个author现如今已经(成大牛)退休了吧!)


我发现的一些安全问题

  • 重复答题
  • 参加答题计次不对
  • 重复领奖
  • 库存超发
  • 前后端状态码的约定问题

问题分析、解决方案:

这些问题对于C端来说都是非常见的问题,无非就是增加校验,加分布式锁嘛

  • 重复答题 一直答题的原因是后端接口没有做校验,因此可以一直调用接口一直答题抽奖。解决方式:后端把校验逻辑加上,加上校验逻辑之后还通过setIfAbsent对同一用户操作加了锁(防止并发问题)

    // 业务逻辑校验
    if(!check(data)){
    	return Response.error(503, "服务器繁忙,请稍后再试!");
    }
    //防止同一个用户重复提交,并发绕过校验的问题
    if(!redisTemplate.opsForValue().setIfAbsent(userId,userId, Duration.ofMillis(1000))){
       return Response.error(503, "服务器繁忙,请稍后再试!");
     }
    

    各位大佬也看看setIfAbsent用得没啥问题吧!!!!!

  • 参加答题计次不对 原来的逻辑如下(伪代码):

    //从map中获取次数,获取成功则加1 
    //static HashMap<String, AtomicInteger> countMap= new HashMap<String, AtomicInteger>();
    
    if (countMap.get(key) == null) {
            // 統計每日答題記錄
            answerTimes = answerActivityRecordService.count(wrapper);
            ActivityMemoryObject.answerLimit.put(key, new AtomicInteger(answerTimes));
    } else {
            AtomicInteger curent = countMap.get(key);
            answerTimes = curent.incrementAndGet();
    }
    //校验通过执行业务逻辑
    if(checkTimes(answerTimes)){
    	save();
    }
    

    咋眼看上去没啥问题,仔细看也没啥问题。如果正常操作的话确实没问题。问题就在没执行业务代码次数也加1了,比如我这个活动参加次数限制是5次,可能我缓存的参加都达到10多次了,我去数据库把活动次数增加,这时候再去参加活动,对比次数就出问题了(当然这种情况只有测试的时候才有!)。

    对于上面的逻辑做了两点调整
    1.用HashMap 只有在系统重启的时候才会被清除,使用弱引用线程安全的map这样jvm在执行gc的时候就会清除缓存,防止缓存堆积:countMap= new ConcurrentReferenceHashMap<String, AtomicInteger>();
    2.执行了业务才加1

  • 重复领奖 接口没有做以领取的校验,导致一直掉接口可以一直领取,修复方式请求接口的时候查询一次这条数据的领取状态,如果不是未领取则直接返回。这样改了之后还有可能存在并发问题,多个线程同时进来这个校验逻辑肯定会校验通过。领取成功之后不是还要更新状态吗,这个地方我也做了如下修改,利用了一个mysql update 实现一个乐观锁

    //修改前,直接根据id更新数据
    updateById(id);
    //其他业务逻辑
    down();
    
    //修改后,条件增加 期望的状态值,算是一个乐观锁吧
    if(!updateByIdAndStatuas(id,originStatus)){
    	throw new RuntimeExcption("你已经抽过奖了!");
    }
    down();
    
  • 库存超发 以前啥限制也没做,这里加了一个redis的分布式锁,获取锁失败进行一个事物回滚!

  • 前后端状态码的约定问题 后端都报错了,返了50x状态码了,前端还在从里面拿数据,接着下一步操作!哎难受哦!传些undifined到后端。。。。。。。。