java中的接口安全整体实现(重点)

开发过程中,避免不了调用第三方接口和调用自己的接口,那么我们如何保证自己的接口安全,减少被攻击的可能性,减少被恶意请求的次数呢?

在学习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;
    }

希望对大家有所帮助,有不懂的可以评论区问我,刚开始写博客的小白一枚,不好的地方请见谅。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值