问题虽然很常见,也在这儿记录分享一下吧
项目简述:
最近我收到个任务是要将我们的答题活动,对接放到微信公众号中去,微信和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= newConcurrentReferenceHashMap
<String, AtomicInteger>();
2.执行了业务才加1 -
重复领奖
接口没有做以领取的校验,导致一直掉接口可以一直领取,修复方式请求接口的时候查询一次这条数据的领取状态,如果不是未领取则直接返回。这样改了之后还有可能存在并发问题
,多个线程同时进来这个校验逻辑肯定会校验通过。领取成功之后不是还要更新状态吗,这个地方我也做了如下修改,利用了一个mysql update 实现一个乐观锁
//修改前,直接根据id更新数据 updateById(id); //其他业务逻辑 down(); //修改后,条件增加 期望的状态值,算是一个乐观锁吧 if(!updateByIdAndStatuas(id,originStatus)){ throw new RuntimeExcption("你已经抽过奖了!"); } down();
-
库存超发
以前啥限制也没做,这里加了一个redis的分布式锁,获取锁失败进行一个事物回滚! -
前后端状态码的约定问题
后端都报错了,返了50x状态码了,前端还在从里面拿数据,接着下一步操作!哎难受哦!传些undifined到后端。。。。。。。。