开发过程中,避免不了调用第三方接口和调用自己的接口,那么我们如何保证自己的接口安全,减少被攻击的可能性,减少被恶意请求的次数呢?
在学习alipay支付时,大概使用签名的方式来实现的,所以我们也模拟使用签名的方式。在前端(在header中带到后端)和后端(存入redis中)都生成一个相同的签名,在网关中,我们可以先调用接口安全的方法,如果签名相同,则验签成功,继续做接口调用操作。
大概有以下几个关键点:
1.如果请求被抓包,篡改了参数怎么办?
解决方式:前端发请求时,传入发请求时的时间戳,后台在进行接口安全验证之前,先进行接口调用时间的判断,如果超过10s(举例),则直接打回请求
2.接口安全的实现
2.1 接口安全的几个关键参数(随机数,请求的参数列表)
2.2 步骤如下
1.后端随机生成100(举例)个6位数的随机数,前端在发正式请求前,先发一个从后端获取随机数的请求(不在前端生成随机数的原因是,业务尽量都在后台做),获取到随机数,然后利用随机数和请求所需要的参数,使用MD5加密方式,生成一个签名,生成签名代码如下:
//引入md5的cdn
<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.min.js"></script>
//将参数排序然后获取sign
function getSign(random) {
let arr = new Array();
let num = 1;
let str = "";
let newArr = [random];
arr[0] = random
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
arr[num] = arguments[i];
num++;
}
// console.log("数组:"+arr);
// console.log("升序后的数组:"+arr.sort());
let newArr = arr.sort();
for (var i = 1; i < newArr.length; i++) {
str += newArr[i] + "&"
}
}
str = str + newArr[0];
// console.log("排序后的字符串" + str)
//md5.js库,已经封装好了md5加密方式,直接调用即可
let sign = md5(str);
// console.log(sign);
return sign
}
2.前端header中带入接口安全所需参数,具体代码如下:
async getCarProduct() {
if (this.userSession == null) {
alert("请先登陆!");
return;
}
//访问数据库
await this.getRandom();
let timeStamp = new Date().getTime();
//调用获取sign的方法
let sign = getSign(this.random, "uid=" + this.userSession.id);
axios({
url: "http://localhost:8080/getCarListByUid",
methods: "post",
headers: {
"timeStamp": timeStamp,
"random": this.random,
"sign": sign
},
params: {
uid: this.userSession.id
}
})
.then((res) => {
console.log(res);
})
},
3.后台使用header中的参数值,和请求的参数列表,也使用md5生成一个签名,代码如下:
/**
* 拼接参数,返回sign
*/
public String getSign(int random, String... params) {
logger.info("InterfaceSafe getRandomForRedis params:" + random + "," + params);
System.out.println("InterfaceSafe random:" + random);
String str = "";
Map map = new TreeMap();
//params此时为数组 而且是动态的
if (params.length > 0) {
for (String s : params) {
//参数数组的形式为"username=abc", "password=123", "type=1"
//把数组分割,并且拼接
String key = StringUtils.substringBefore(s, "=");
String value = StringUtils.substringAfter(s, "=");
//TreeMap会帮我们自动按照Asic码排序
map.put(key, value);
}
Set<String> set = map.keySet();
for (String key : set) {
String value = (String) map.get(key);
str += key + "=" + value + "&";
}
}
str = str + random;
// id=1&123456
//对密码进行 md5 加密
System.out.println("加密前:"+str);
String md5Password = DigestUtils.md5DigestAsHex(str.getBytes());
logger.debug("InterfaceSafe getRandomForRedis params:" + random + "," + params + "result:" + md5Password);
// System.out.println("加密后:"+md5Password);
return md5Password;
}
4.如果我们的签名匹配成功,则需要将这个随机数从redis中删除,代码如下:
/**
* 如果sign匹配成功,就删除该随机数
*/
public void delRandomForRedis(int random) {
logger.info("InterfaceSafe delRandomForRedis params:" + ",random:" + random);
List list = JSONArray.parseArray(redisTemplate.opsForValue().get("random").toString());
for (int i = 0; i < list.size(); i++) {
if ((int) list.get(i) == random) {
// System.out.println("随机数为"+list.get(i)+"序号为:"+i);
list.remove(i);
if (list.size() < 10) {
//如果总量小于10条,就重新往里面添加100个随机数
String addList = this.produceRandomToRedis();
redisTemplate.opsForValue().set("random", addList);
return;
}
redisTemplate.opsForValue().set("random", JSON.toJSONString(list));
return;
}
}
}
5.生成随机数的代码:
/**
* 往redis中生成100个随机数
*/
public String produceRandomToRedis() {
logger.info("InterfaceSafe produceRandomToRedis start...");
List randomList = new ArrayList();
for (int i = 0; i < 100; i++) {
randomList.add((int) ((Math.random() * 9 + 1) * 100000));
}
String randomListStr = JSON.toJSONString(randomList);
logger.debug("InterfaceSafe produceRandomToRedis result:" + randomListStr);
redisTemplate.opsForValue().set("random", randomListStr);
return randomListStr;
}
6.获取随机数的代码
/**
* 获取一个随机数
*/
public int getRandomForRedis() {
logger.info("InterfaceSafe getRandomForRedis start...");
List list = JSONArray.parseArray(redisTemplate.opsForValue().get("random").toString());
int num = (int) (Math.random() * list.size());
// System.out.println("list内容:"+list);
int random = (int) list.get(num);
logger.info("InterfaceSafe getRandomForRedis result:" + random);
return random;
}
7.在网关中拿到header中的值,和参数列表,调用判断接口安全的方法,
接口安全的代码实现:
/**
* 验证是否是正确的请求
*/
public ResultMsg isRightRequest(HttpServletRequest req,String...params){
ResultMsg resultMsg = new ResultMsg();
//获取前端传来的时间戳
long times = Long.parseLong(req.getHeader("timeStamp"));
//获取前端传来的random,然后和参数列表一起传进去,生成后端的签名
int random = Integer.parseInt(req.getHeader("random"));
//获取前端的签名
String frontSign = req.getHeader("sign");
//验证是否超时,超时则为错误请求
boolean timeOut = this.timeOut(times);
System.out.println("times:"+times+",timeOut:"+timeOut);
if(timeOut){
resultMsg.setCode("500");
resultMsg.setMsg("非法请求,超时");
return resultMsg;
}
System.out.println("random:"+random);
String sign = this.getSign(random,params);
System.out.println("后端的签名:"+sign);
System.out.println("前端的签名:"+frontSign);
//签名不同则是非法请求
if(!sign.equals(frontSign)){
resultMsg.setCode("500");
resultMsg.setMsg("非法请求,签名错误");
return resultMsg;
}
//如果两个签名相同就删除这个随机数,然后继续正常请求
this.delRandomForRedis(random);
resultMsg.setCode("200");
resultMsg.setMsg("正确请求");
return resultMsg;
}
希望对大家有所帮助,有不懂的可以评论区问我,刚开始写博客的小白一枚,不好的地方请见谅。
版权归原作者 五颜六色的屁 所有, 如有侵权,请联系我们删除。