摘要:最近正在負責一個新平臺的構建和開發,有一個場景需要對應用做新增,修改和撤回的操作起先是因為之前寫過類型的功能,不想在和以前一樣一個操作類型一個,覺得代碼太過冗余了。
最近正在負責一個新平臺的構建和開發,有一個場景需要對應用做新增,修改和撤回的操作
起先是因為之前寫過類型的功能,不想在和以前一樣一個操作類型一個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安全策略的想法,如有異議或新的方式歡迎評論留言。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102750.html
摘要:幾年前,行業預測分析人員表示,一旦企業決定了他們的云計算戰略,他們將會首先構建私有云,并在以后根據需要添加公共云服務。如果要在本地實施容器或作為云計算部署的一部分實施容器,則需要確保其工作負載是安全的。幾年前,行業預測分析人員表示,一旦企業決定了他們的云計算IT戰略,他們將會首先構建私有云,并在以后根據需要添加公共云服務。但這種事情并沒有發生。事實證明,采用云計算可以盡快讓組織的董事會分配資...
摘要:綜上所述,認為沒有提供的保護,用戶會過得更好安全研究人員并不完全反對這一決定。內容安全策略是一個額外的安全層,用于檢測并削弱某些特定類型的攻擊,包括跨站腳本和數據注入攻擊等。 這是關于web安全性系列文章的第 三 篇,其它的可點擊以下查看: Web 應用安全性: 瀏覽器是如何工作的 Web 應用安全性: HTTP簡介 目前,瀏覽器已經實現了大量與安全相關的頭文件,使攻擊者更難利用漏...
摘要:文件完整性監測持續監控您的云服務器,保護重要的系統二進制文件和配置文件不會受到未經授權的或惡意的變更。首先會記錄下云服務器系統的清潔狀態,作為基準。您可以通過一個在線管理控制臺,監控所有的云服務器。 DEVSECOPS 所面臨的挑戰 敏捷開發和 DevOps 方法的出現使軟件開發的速度與質量都有所提升,但它們不經意地也為安全機構增壓不少。從前的安全策略是基于靜態數據的,而在產品上線前才...
閱讀 3923·2021-11-24 09:38
閱讀 3099·2021-11-17 09:33
閱讀 3874·2021-11-10 11:48
閱讀 1243·2021-10-14 09:48
閱讀 3133·2019-08-30 13:14
閱讀 2554·2019-08-29 18:37
閱讀 3396·2019-08-29 12:38
閱讀 1421·2019-08-29 12:30