前言
此篇文章是系列的第七篇,主要讲一些安全性相关联的问题,对应的源码部分参见Github源码第七部分。下面是一些相关联的文章。
章节名称 | 博客地址 |
---|---|
安装部署Redis | 集成Redis(已完结) |
页面登陆功能设计 | 登录功能设计(更新优化中) |
秒杀页面具体设计 | 秒杀详情页(已完结) |
JMeter初级压测学习 | Jmeter压测入门学习(已完结) |
页面优化设计 | 页面优化设计(已完结) |
接口优化 | RabbbitMq接口优化(已完结) |
图形验证码等 | 图形验证码及接口防刷(更新优化中) |
对于秒杀模块的安全部分设计之前就有过学习,但是当时没有进行一个系统的记录,所以这里再度进行一个具体的学习设计与记录,是对自己学习的一个检测同时也能够帮助在学习这部分知识的小伙伴提供一个帮助。
正文
对于秒杀模块我们在考虑完了性能以后(对于第六节的页面优化设计和第七节的接口优化设计)剩下就是考虑安全性。
- 我们知道所有的页面都需要有一个URL进行访问,有时候我们的商品的URL前缀可能是固定的例如给定的商品是属于哪一个品系,哪一个分类的下面,再添加上我们的商品的
id
信息很有可能就可以推算出这个秒杀商品的URL
地址信息。所以我们要对地址进行隐藏处理的同时还要动态生产秒杀的地址信息。 - 对于验证码的生成操作,因为对于秒杀活动来说都算是比较优惠的,就难免会有一些人使用到机器人对接口进行狂刷,利用一些特殊的手段对商品重复秒杀,然后再销售,谋取利益。所以我们设置验证码,需要人工进行判断输出,防止机器人的重复刷接口。
- 除了能够防止机器人,还能够防止再瞬间的高并发访问,因为秒杀开始时候就会有大量的访问到达,这个时候设置一个验证码,对于不同的人输入的速度有差异,就可以使得一瞬间的访问分散到几秒之内完成,减轻网络和数据库的压力
- 接口限流防刷,对于我们的接口我们在之前的测试中有过进行压测处理,查看每个接口在特定的时间里面(每秒)能够承受住多大的并发,所以就需要我们来进行一个接口限流防刷的设计,因为可能会有一些恶意的软件对这个接口进行一直访问,导致一些其他正常的访问者不能够正常访问该网站。
秒杀接口地址隐藏处理
接口改造:之前我们的接口在前端的页面中都能够通过检查来查看这些端口的地址,如下图所示,这些URL都可以直接用来访问,所以可以进行以下的改造。
- 在秒杀之前,我们不再是直接将秒杀地址写好,而是通过请求接口来获取到秒杀的地址,带上
PathVariable
参数。 - 我们通过添加生成接口的地址,来访问这个地址获取到接口的信息。
- 对于后端收到的秒杀的请求,需要先验证
PathVariable
参数的正确性再判断是否继续进行后续的操作。
具体实现
之前我们点击秒杀时候就是直接开始了秒杀,开始了库存的判断和订单的创建,现在我们对秒杀按钮进行一个地址生成的设计:
<button class="btn btn-primary btn-block" type="button" id="buyButton"onclick="getMiaoPath()">立即秒杀</button>
function getMiaoPath() {
g_showLoading();
var goId=$("#goodsId").val();
$.ajax({
//先请求到地址的生成页面 返回一个UUID+MD5的String
url:"/miaosha/path",
type:"GET",
data:{
goodsId:goId
},
success:function(data){
if(data.code == 0){
// 将这个信息传递秒杀页面
var path = data.data;
doMiaosha(path);
}else{
layer.msg(data.msg);
}
},
error:function(){
layer.msg("客户端请求有误");
}
});
}
对于我们的path函数就是生成一个经过MD5加密过后的UUID。
/**
* 获取到秒杀的接口信息。
* @param model
* @param user
* @param goodsId
* @date 2020-5-6
*/
@RequestMapping(value="/path", method=RequestMethod.GET)
@ResponseBody
public Result<String> getMiaoPath(Model model,MiaoshaUser user,
@RequestParam("goodsId")long goodsId) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
String path=miaoshaService.createMiaoshaPath(user,goodsId);
return Result.success(path);
}
// 调用的函数
public String createMiaoshaPath(MiaoshaUser user, long goodsId) {
String str= MD5Util.md5(UUIDUtil.uuid())+"123456";
redisService.set(MiaoshaKey.MiaoshaPath,""+user.getId()+"_"+goodsId,str);
return str;
}
在完成以上以后,我们进行进行秒杀时候,首先会接收到path并进行处理判断:
@RequestMapping(value="/{path}/do_miaosha", method=RequestMethod.POST)
@ResponseBody
public Result<Integer> miaosha(Model model, MiaoshaUser user,
@RequestParam("goodsId")long goodsId,
@PathVariable("path")String path) {
model.addAttribute("user", user);
if(user == null) {
return Result.error(CodeMsg.SESSION_ERROR);
}
// 调用之前的秒杀service 取出来对应的path 匹配时候返回true
boolean check=miaoshaService.checkedpath(user,goodsId,path);
if(!check