资讯专栏INFORMATION COLUMN

【web安全】API的安全策略

RobinQu / 999人阅读

摘要:最近正在负责一个新平台的构建和开发,有一个场景需要对应用做新增,修改和撤回的操作起先是因为之前写过类型的功能,不想在和以前一样一个操作类型一个,觉得代码太过冗余了。

最近正在负责一个新平台的构建和开发,有一个场景需要对应用做新增,修改和撤回的操作
起先是因为之前写过类型的功能,不想在和以前一样一个操作类型一个api,觉得代码太过冗余了。
于是有了以下的构思

第一版
将当前界面所有api请求,合并成一个request,以type作为操作类型的区分,data为提交的数据

这样当前界面所有操作都使用一个接口来处理,并且问题统一处理

处理token失效

处理catch

处理通信成功后都通知

处理权限

优化版
当设计成第一版后,我觉得操作类型暴露在外面有些不妥,起先想的是后端生成随机码和对应的加密值,通过解密拿到方法名。
后来优化了一下,加入了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);
  }

这是返回的结构

    前端就不上代码了,稍微说下应该都能明白
    1. 进入界面的时候,请求getConfig
    2. 前端拿到数据进行解密
    3. 操作界面的时候,发送操作码和数据
    4. 请求完成,拿到新的操作码进行本地更新,并对之前的操作作出反应(数据更新/界面跳转/弹框提示等)

延伸版
获取界面配置,可以放在一个任何界面都会访问的地方,统一处理,后端配好路由的url即可

解决/预防了哪些问题
1.代码冗余问题
2.爬虫问题(由于所有的操作入参都是动态返回且随机生成,爬虫们没法按着一个接口和数据爬取数据,增大了难度)
3.非正常的访问

以上就是我对API安全策略的想法,如有异议或新的方式欢迎评论留言。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/102750.html

相关文章

  • 论微服务安全

    摘要:微服务能够为应用程序设计提供一种更具针对性范围性与模块性的实现方案。安全微服务部署模式可谓多种多样但其中使用最为广泛的当数每主机服务模式。在微服务环境下,安全性往往成为最大的挑战。不同微服务之间可通过多种方式建立受信关系。 每个人都在讨论微服务,每个人也都希望能够实现微服务架构,而微服务安全也日渐成为大家关注的重要问题。今天小数与大家分享的文章,就从应用层面深入探讨了应对微服务安全挑战...

    plokmju88 评论0 收藏0
  • 如何构建安全企业混合云

    摘要:几年前,行业预测分析人员表示,一旦企业决定了他们的云计算战略,他们将会首先构建私有云,并在以后根据需要添加公共云服务。如果要在本地实施容器或作为云计算部署的一部分实施容器,则需要确保其工作负载是安全的。几年前,行业预测分析人员表示,一旦企业决定了他们的云计算IT战略,他们将会首先构建私有云,并在以后根据需要添加公共云服务。但这种事情并没有发生。事实证明,采用云计算可以尽快让组织的董事会分配资...

    MarvinZhang 评论0 收藏0
  • Web 应用安全性: 使用这些 HTTP 头保护 Web 应用

    摘要:综上所述,认为没有提供的保护,用户会过得更好安全研究人员并不完全反对这一决定。内容安全策略是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本和数据注入攻击等。 这是关于web安全性系列文章的第 三 篇,其它的可点击以下查看: Web 应用安全性: 浏览器是如何工作的 Web 应用安全性: HTTP简介 目前,浏览器已经实现了大量与安全相关的头文件,使攻击者更难利用漏...

    spademan 评论0 收藏0
  • 别让安全问题拖慢了 DevOps!

    摘要:文件完整性监测持续监控您的云服务器,保护重要的系统二进制文件和配置文件不会受到未经授权的或恶意的变更。首先会记录下云服务器系统的清洁状态,作为基准。您可以通过一个在线管理控制台,监控所有的云服务器。 DEVSECOPS 所面临的挑战 敏捷开发和 DevOps 方法的出现使软件开发的速度与质量都有所提升,但它们不经意地也为安全机构增压不少。从前的安全策略是基于静态数据的,而在产品上线前才...

    forsigner 评论0 收藏0

发表评论

0条评论

RobinQu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<