【web平安】API的平安策略

近来正在担任一个新平台的构建和开辟,有一个场景须要对运用做新增,修正和撤回的操纵

起先是因为之前写过范例的功用,不想在和之前一样一个操纵范例一个api,以为代码太甚冗余了。

因而有了以下的构想

  • 初版
    将当前界面一切api要求,合并成一个request,以type作为操纵范例的辨别,data为提交的数据
    《【web平安】API的平安策略》
    如许当前界面一切操纵都运用一个接口来处置惩罚,而且题目一致处置惩罚

    1. 处置惩罚token失效
    2. 处置惩罚catch
    3. 处置惩罚通讯胜利后都关照
    4. 处置惩罚权限
  • 优化版
    当设想成初版后,我以为操纵范例暴露在表面有些不妥,起先想的是后端天生随机码和对应的加密值,经由过程解密拿到要领名。
    厥后优化了一下,加入了url泉源的推断,还能防备postman的进击
    后端代码以下:

    redisImp为redis
    utils为东西类
    token和权限的搜检放在了外层,进入要领的都当做token和权限经由过程的
    
    const apiPrefix = 'ApiType:';
    // 经由过程viewConfig天生对应设置
    async function generateConfig (owner, viewConfig) {
        var viewName = viewConfig.name; // 界面称号
        var viewMethods = viewConfig.methods; // 界面所支撑的操纵要领
    
        let key = apiPrefix + owner + ':' + viewName;
        await redisImp.del(key);
        let para = [], config = [], secret = [];
        // 天生10个长度为12的随机码
        for (var i = 0; i < 10; i++) {
          var randomKey = utils.generateRandomStr(12);
          config.push(randomKey);
        }
        // 天生三个10一下的数字
        var random1 = Math.ceil(Math.random() * 10);
        var random2 = Math.ceil(Math.random() * 10);
        var random3 = Math.ceil(Math.random() * 10);
        // todo 搜检3个随机数是不是相称
        var randomList = [random1, random2, random3];
        // 天生随机码和操纵要领的关联数据
        viewMethods.forEach(function (value, index) {
          para.push(config[randomList[index]]);
          para.push(value);
          secret.push(randomList[index]);
        })
        // aes加密
        var enc = utils.cryptedAES(secret.toString());
        let redisResult = await redisImp.hSet(key, para);
        if (redisResult.code === 200) {
          return {
            apis: config,
            secret: enc
          }
        }
        return null;
      }
      // 猎取界面的设置
      function getViewConfig (ctx) {
        var referer = ctx.request.header.referer; // 原始url
        var origin = ctx.request.header.origin; // 泉源
        var config;
        if (!referer || !origin) {
            // todo 处置惩罚非常接见
            return config;
        } else {
            var fontUrl = referer.replace(origin, '').split('?'); // 去除domain和url参数后的途径
            switch (fontUrl[0]) {
              case '/app/base': {
                config = {
                  name: 'appBase', // 界面称号
                  methods: ['add', 'modify', 'retract'] // 界面操纵权限
                }
                break;
              }
              default: {
              // todo 处置惩罚非常进击
              }
            }
        }
        return config;
      }
      
      // 猎取设置,暴露给前端的api接口
      const getConfig = async (ctx) => {
        const fName = _name + 'getConfig';
        lifecycleLog.info('[Enter] ' + fName);
        // 猎取当前用户id
        const redisResult = await redis.GetTokenValue(ctx, 'id');
        let owner;
        if (redisResult.code === 200) {
            owner = redisResult.data;
        } else {
            ctx.body = redisResult;
            return;
        }
        // 猎取界面设置
        var viewConfig = getViewConfig(ctx);
        if (viewConfig) {
          var result = await generateConfig(owner, viewConfig);
          if (result) {
            // 天生胜利后返回给前端
            ctx.body = Object.assign({code: 200}, result);
          } else {
            ctx.body = controller.dataError();
          }
        } else {
          ctx.body = controller.dataError();
        }
        lifecycleLog.info('[Return] ' + fName);
      }
    
      const appBase = require('./appBase')
      // 处置惩罚运用界面的接口
      const handleAppBaseData = async (ctx) => {
        const fName = _name + 'handleAppBaseData';
        lifecycleLog.info('[Enter] ' + fName);
    
        var viewConfig = getViewConfig(ctx);
        if (viewConfig) {
          const name = ctx.request.body.name; // 前端传过来的操纵码
          const para = ctx.request.body.data; // 前端传过来的数据
          let data;
          try {
            data = JSON.stringify(para);
          } catch (err) {
            ctx.body = controller.dataError();
            return;
          }
          // 考证数据完整性
          if (controller.dataMissed(ctx, fName, ctx.request.body, name + data)) {
            return;
          }
    
          const redisResult = await redis.GetTokenValue(ctx, 'id');
          let owner;
          if (redisResult.code === 200) {
             owner = redisResult.data;
           } else {
             ctx.body = redisResult;
             return;
           }
          // 从redis拿到当前用户在当前界面的操做范例
          let apiType = await redisImp.hGet(apiPrefix + owner + ':' + viewConfig.name, name);
          if (apiType.code === 200) {
            if (apiType.data.length) {
              var methods = apiType.data[0];
              // 增加操纵
              if (methods === 'add') {
                await appBase.add(ctx, para, owner);
              } else {
                let option = {
                  _id: para._id,
                  owner: owner
                };
                // 检测该用户是不是具有该app
                const gameResult = await commonModel.getInfo(ctx, collection, option);
                if (gameResult) {
                  if (gameResult.code === 200) {
                    var gameDoc = gameResult.info['_doc'];
                  } else {
                    ctx.body = controller.dataError();
                    return;
                  }
                } else {
                  ctx.body = controller.serverError();
                  return;
                }
                // 修正操纵
                if (methods === 'modify') {
                  await appBase.modify(ctx, para, gameDoc);
                } else if (methods === 'retract') { // 撤回炒作
                  await appBase.retract(ctx, gameDoc);
                } else {
                  ctx.body = controller.dataError();
                  return;
                }
              }
              // 假如入库胜利,则将新一轮的操纵码反给前端
              if (ctx.body.code === 200) {
                var result = await generateConfig(owner, viewConfig);
                ctx.body = Object.assign(ctx.body, result);
              }
            } else {
              ctx.body = controller.dataError();
            }
          } else {
            ctx.body = controller.serverError();
          }
        } else {
          ctx.body = controller.dataError();
        }
        lifecycleLog.info('[Return] ' + fName);
      }
    
    
    • 这是返回的构造
      《【web平安】API的平安策略》
    前端就不上代码了,轻微说下应当都能邃晓
    1. 进入界面的时刻,要求getConfig
    2. 前端拿到数据举行解密
    3. 操纵界面的时刻,发送操纵码和数据
    4. 要求完成,拿到新的操纵码举行当地更新,并对之前的操纵作出反应(数据更新/界面跳转/弹框提醒等)
  • 延长版
    猎取界面设置,能够放在一个任何界面都邑接见的处所,一致处置惩罚,后端配好路由的url即可
  • 处理/预防了哪些题目
    1.代码冗余题目
    2.爬虫题目(因为一切的操纵入参都是动态返回且随机天生,爬虫们没法按着一个接口和数据爬取数据,增大了难度)
    3.非正常的接见

以上就是我对API安全策略的主意,若有贰言或新的体式格局迎接批评留言。

    原文作者:Sumon
    原文地址: https://segmentfault.com/a/1190000018587494
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞