摘要:對(duì)于內(nèi)容型的公司,數(shù)據(jù)的安全性很重要。背景目前通過中的網(wǎng)頁(yè)分析后,我們的數(shù)據(jù)安全性做的較差,有以下幾個(gè)點(diǎn)存在問題網(wǎng)站的數(shù)據(jù)通過最早期的前后端分離來實(shí)現(xiàn)。比如當(dāng)前的日期為,那么線性變換的為,為。
之前在上家公司的時(shí)候做過一些爬蟲的工作,也幫助爬蟲工程師解決過一些問題。然后我寫過一些文章發(fā)布到網(wǎng)上,之后有一些人就找我做一些爬蟲的外包,內(nèi)容大概是爬取小紅書的用戶數(shù)據(jù)和商品數(shù)據(jù),但是我沒做。我覺得對(duì)于國(guó)內(nèi)的大數(shù)據(jù)公司沒幾家是有真正的大數(shù)據(jù)量,而是通過爬蟲工程師團(tuán)隊(duì)不斷的去各地爬取數(shù)據(jù),因此不要以為我們的數(shù)據(jù)沒價(jià)值,對(duì)于內(nèi)容型的公司來說,數(shù)據(jù)是可信競(jìng)爭(zhēng)力。那么我接下來想說的就是網(wǎng)絡(luò)和數(shù)據(jù)的安全性問題。背景
對(duì)于內(nèi)容型的公司,數(shù)據(jù)的安全性很重要。對(duì)于內(nèi)容公司來說,數(shù)據(jù)的重要性不言而喻。比如你一個(gè)做在線教育的平臺(tái),題目的數(shù)據(jù)很重要吧,但是被別人通過爬蟲技術(shù)全部爬走了?如果核心競(jìng)爭(zhēng)力都被拿走了,那就是涼涼。再比說有個(gè)獨(dú)立開發(fā)者想抄襲你的產(chǎn)品,通過抓包和爬蟲手段將你核心的數(shù)據(jù)拿走,然后短期內(nèi)做個(gè)網(wǎng)站和 App,短期內(nèi)成為你的勁敵。
目前通過 App 中的 網(wǎng)頁(yè)分析后,我們的數(shù)據(jù)安全性做的較差,有以下幾個(gè)點(diǎn)存在問題:
網(wǎng)站的數(shù)據(jù)通過最早期的前后端分離來實(shí)現(xiàn)。稍微學(xué)過 Web 前端的工程師都可以通過神器 Chrome 分析網(wǎng)站,進(jìn)而爬取需要的數(shù)據(jù)。打開 「Network」就可以看到網(wǎng)站的所有網(wǎng)絡(luò)請(qǐng)求了,哎呀,不小心我看到了什么?沒錯(cuò)就是網(wǎng)站的接口信息都可以看到了。比如 “detail.json?itemId=141529859”?;蛘吣愕木W(wǎng)站接口有些特殊的判斷處理,將一些信息存儲(chǔ)到 sessionStorage、cookie、localStorage 里面,有點(diǎn)前端經(jīng)驗(yàn)的爬蟲工程師心想”嘿嘿嘿,這不是在裸奔數(shù)據(jù)么“?;蛘哂行﹨?shù)是通過 JavaScript 臨時(shí)通過函數(shù)生成的。問題不大,工程師也可以對(duì)網(wǎng)頁(yè)元素進(jìn)行查找,找到關(guān)鍵的 id、或者 css 類名,然后在 "Search“ 可以進(jìn)行查找,找到對(duì)應(yīng)的代碼 JS 代碼,點(diǎn)擊查看代碼,如果是早期前端開發(fā)模式那么代碼就是裸奔的,跟開發(fā)者在自己的 IDE 里面看到的內(nèi)容一樣,有經(jīng)驗(yàn)的爬蟲就可以拿這個(gè)做事情,因此安全性問題亟待解決。
想知道 Chrome 更多的調(diào)試使用技巧,看看這篇文章
App 的數(shù)據(jù)即使采用了 HTTPS,但是對(duì)于專業(yè)的抓包工具也是可以直接拿到數(shù)據(jù)的,因此 App 的安全問題也可以做一些提高,具體的策略下文會(huì)講到。
想知道 Charles 的更多使用技巧,可以看看這篇文章
爬蟲手段目前爬蟲技術(shù)都是從渲染好的 html 頁(yè)面直接找到感興趣的節(jié)點(diǎn),然后獲取對(duì)應(yīng)的文本
有些網(wǎng)站安全性做的好,比如列表頁(yè)可能好獲取,但是詳情頁(yè)就需要從列表頁(yè)點(diǎn)擊對(duì)應(yīng)的 item,將 itemId 通過 form 表單提交,服務(wù)端生成對(duì)應(yīng)的參數(shù),然后重定向到詳情頁(yè)(重定向過來的地址后才帶有詳情頁(yè)的參數(shù) detailID),這個(gè)步驟就可以攔截掉一部分的爬蟲開發(fā)者
解決方案 制定出Web 端反爬技術(shù)方案本人從這2個(gè)角度(網(wǎng)頁(yè)所見非所得、查接口請(qǐng)求沒用)出發(fā),制定了下面的反爬方案。
使用HTTPS 協(xié)議
單位時(shí)間內(nèi)限制掉請(qǐng)求次數(shù)過多,則封鎖該賬號(hào)
前端技術(shù)限制 (接下來是核心技術(shù))
# 比如需要正確顯示的數(shù)據(jù)為“19950220” 1. 先按照自己需求利用相應(yīng)的規(guī)則(數(shù)字亂序映射,比如正常的0對(duì)應(yīng)還是0,但是亂序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)制作自定義字體(ttf) 2. 根據(jù)上面的亂序映射規(guī)律,求得到需要返回的數(shù)據(jù) 19950220 -> 17730220 3. 對(duì)于第一步得到的字符串,依次遍歷每個(gè)字符,將每個(gè)字符根據(jù)按照線性變換(y=kx+b)。線性方程的系數(shù)和常數(shù)項(xiàng)是根據(jù)當(dāng)前的日期計(jì)算得到的。比如當(dāng)前的日期為“2018-07-24”,那么線性變換的 k 為 7,b 為 24。 4. 然后將變換后的每個(gè)字符串用“3.1415926”拼接返回給接口調(diào)用者。(為什么是3.1415926,因?yàn)閷?duì)數(shù)字偽造反爬,所以拼接的文本肯定是數(shù)字的話不太會(huì)引起研究者的注意,但是數(shù)字長(zhǎng)度太短會(huì)誤傷正常的數(shù)據(jù),所以用所熟悉的 Π) ?``` 1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645 02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638 20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624 ?``` # 前端拿到數(shù)據(jù)后再解密,解密后根據(jù)自定義的字體 Render 頁(yè)面 1. 先將拿到的字符串按照“3.1415926”拆分為數(shù)組 2. 對(duì)數(shù)組的每1個(gè)數(shù)據(jù),按照“線性變換”(y=kx+b,k和b同樣按照當(dāng)前的日期求解得到),逆向求解到原本的值。 3. 將步驟2的的到的數(shù)據(jù)依次拼接,再根據(jù) ttf 文件 Render 頁(yè)面上。
后端需要根據(jù)上一步設(shè)計(jì)的協(xié)議將數(shù)據(jù)進(jìn)行加密處理
下面以 Node.js 為例講解后端需要做的事情
首先后端設(shè)置接口路由
獲取路由后面的參數(shù)
根據(jù)業(yè)務(wù)需要根據(jù) SQL 語(yǔ)句生成對(duì)應(yīng)的數(shù)據(jù)。如果是數(shù)字部分,則需要按照上面約定的方法加以轉(zhuǎn)換。
將生成數(shù)據(jù)轉(zhuǎn)換成 JSON 返回給調(diào)用者
// json var JoinOparatorSymbol = "3.1415926"; function encode(rawData, ruleType) { if (!isNotEmptyStr(rawData)) { return ""; } var date = new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var encodeData = ""; for (var index = 0; index < rawData.length; index++) { var datacomponent = rawData[index]; if (!isNaN(datacomponent)) { if (ruleType < 3) { var currentNumber = rawDataMap(String(datacomponent), ruleType); encodeData += (currentNumber * month + day) + JoinOparatorSymbol; } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } else { encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol; } } else if (ruleType == 4) { encodeData += rawDataMap(String(datacomponent), ruleType); } } if (encodeData.length >= JoinOparatorSymbol.length) { var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length); if (lastTwoString == JoinOparatorSymbol) { encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length); } }
//字體映射處理 function rawDataMap(rawData, ruleType) { if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) { return; } var mapData; var rawNumber = parseInt(rawData); var ruleTypeNumber = parseInt(ruleType); if (!isNaN(rawData)) { lastNumberCategory = ruleTypeNumber; //字體文件1下的數(shù)據(jù)加密規(guī)則 if (ruleTypeNumber == 1) { if (rawNumber == 1) { mapData = 1; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 4; } else if (rawNumber == 4) { mapData = 5; } else if (rawNumber == 5) { mapData = 3; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 9; } else if (rawNumber == 9) { mapData = 7; } else if (rawNumber == 0) { mapData = 0; } } //字體文件2下的數(shù)據(jù)加密規(guī)則 else if (ruleTypeNumber == 0) { if (rawNumber == 1) { mapData = 4; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 3; } else if (rawNumber == 4) { mapData = 1; } else if (rawNumber == 5) { mapData = 8; } else if (rawNumber == 6) { mapData = 5; } else if (rawNumber == 7) { mapData = 6; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } //字體文件3下的數(shù)據(jù)加密規(guī)則 else if (ruleTypeNumber == 2) { if (rawNumber == 1) { mapData = 6; } else if (rawNumber == 2) { mapData = 2; } else if (rawNumber == 3) { mapData = 1; } else if (rawNumber == 4) { mapData = 3; } else if (rawNumber == 5) { mapData = 4; } else if (rawNumber == 6) { mapData = 8; } else if (rawNumber == 7) { mapData = 3; } else if (rawNumber == 8) { mapData = 7; } else if (rawNumber == 9) { mapData = 9; } else if (rawNumber == 0) { mapData = 0; } } else if (ruleTypeNumber == 3) { if (rawNumber == 1) { mapData = ""; } else if (rawNumber == 2) { mapData = ""; } else if (rawNumber == 3) { mapData = ""; } else if (rawNumber == 4) { mapData = ""; } else if (rawNumber == 5) { mapData = ""; } else if (rawNumber == 6) { mapData = ""; } else if (rawNumber == 7) { mapData = ""; } else if (rawNumber == 8) { mapData = ""; } else if (rawNumber == 9) { mapData = ""; } else if (rawNumber == 0) { mapData = ""; } } else{ mapData = rawNumber; } } else if (ruleTypeNumber == 4) { var sources = ["年", "萬", "業(yè)", "人", "信", "元", "千", "司", "州", "資", "造", "錢"]; //判斷字符串為漢字 if (/^[u4e00-u9fa5]*$/.test(rawData)) { if (sources.indexOf(rawData) > -1) { var currentChineseHexcod = rawData.charCodeAt(0).toString(16); var lastCompoent; var mapComponetnt; var numbers = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; var characters = ["a", "b", "c", "d", "e", "f", "g", "h", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]; if (currentChineseHexcod.length == 4) { lastCompoent = currentChineseHexcod.substr(3, 1); var locationInComponents = 0; if (/[0-9]/.test(lastCompoent)) { locationInComponents = numbers.indexOf(lastCompoent); mapComponetnt = numbers[(locationInComponents + 1) % 10]; } else if (/[a-z]/.test(lastCompoent)) { locationInComponents = characters.indexOf(lastCompoent); mapComponetnt = characters[(locationInComponents + 1) % 26]; } mapData = "" + currentChineseHexcod.substr(0, 3) + mapComponetnt + ";"; } } else { mapData = rawData; } } else if (/[0-9]/.test(rawData)) { mapData = rawDataMap(rawData, 2); } else { mapData = rawData; } } return mapData; }
//api module.exports = { "GET /api/products": async (ctx, next) => { ctx.response.type = "application/json"; ctx.response.body = { products: products }; }, "GET /api/solution1": async (ctx, next) => { try { var data = fs.readFileSync(pathname, "utf-8"); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log("fail: " + error); } var data = { code: 200, message: "success", data: { name: "@杭城小劉", year: LBPEncode("1995", rule), month: LBPEncode("02", rule), day: LBPEncode("20", rule), analysis : rule } } ctx.set("Access-Control-Allow-Origin", "*"); ctx.response.type = "application/json"; ctx.response.body = data; }, "GET /api/solution2": async (ctx, next) => { try { var data = fs.readFileSync(pathname, "utf-8"); ruleJson = JSON.parse(data); rule = ruleJson.data.rule; } catch (error) { console.log("fail: " + error); } var data = { code: 200, message: "success", data: { name: LBPEncode("建造師",rule), birthday: LBPEncode("1995年02月20日",rule), company: LBPEncode("中天公司",rule), address: LBPEncode("浙江省杭州市拱墅區(qū)石祥路",rule), bidprice: LBPEncode("2萬元",rule), negative: LBPEncode("2018年辦事效率太高、負(fù)面基本沒有",rule), title: LBPEncode("建造師",rule), honor: LBPEncode("最佳獎(jiǎng)",rule), analysis : rule } } ctx.set("Access-Control-Allow-Origin", "*"); ctx.response.type = "application/json"; ctx.response.body = data; }, "POST /api/products": async (ctx, next) => { var p = { name: ctx.request.body.name, price: ctx.request.body.price }; products.push(p); ctx.response.type = "application/json"; ctx.response.body = p; } };
//路由 const fs = require("fs"); function addMapping(router, mapping){ for(var url in mapping){ if (url.startsWith("GET")) { var path = url.substring(4); router.get(path,mapping[url]); console.log(`Register URL mapping: GET: ${path}`); }else if (url.startsWith("POST ")) { var path = url.substring(5); router.post(path, mapping[url]); console.log(`Register URL mapping: POST ${path}`); } else if (url.startsWith("PUT ")) { var path = url.substring(4); router.put(path, mapping[url]); console.log(`Register URL mapping: PUT ${path}`); } else if (url.startsWith("DELETE ")) { var path = url.substring(7); router.del(path, mapping[url]); console.log(`Register URL mapping: DELETE ${path}`); } else { console.log(`Invalid URL: ${url}`); } } } function addControllers(router, dir){ fs.readdirSync(__dirname + "/" + dir).filter( (f) => { return f.endsWith(".js"); }).forEach( (f) => { console.log(`Process controllers:${f}...`); let mapping = require(__dirname + "/" + dir + "/" + f); addMapping(router,mapping); }); } module.exports = function(dir){ let controllers = dir || "controller"; let router = require("koa-router")(); addControllers(router,controllers); return router.routes(); };
前端根據(jù)服務(wù)端返回的數(shù)據(jù)逆向解密
$("#year").html(getRawData(data.year,log)); // util.js var JoinOparatorSymbol = "3.1415926"; function isNotEmptyStr($str) { if (String($str) == "" || $str == undefined || $str == null || $str == "null") { return false; } return true; } function getRawData($json,analisys) { $json = $json.toString(); if (!isNotEmptyStr($json)) { return; } var date= new Date(); var year = date.getFullYear(); var month = date.getMonth() + 1; var day = date.getDate(); var datacomponents = $json.split(JoinOparatorSymbol); var orginalMessage = ""; for(var index = 0;index < datacomponents.length;index++){ var datacomponent = datacomponents[index]; if (!isNaN(datacomponent) && analisys < 3){ var currentNumber = parseInt(datacomponent); orginalMessage += (currentNumber - day)/month; } else if(analisys == 3){ orginalMessage += datacomponent; } else{ //其他情況待續(xù),本 Demo 根據(jù)本人在研究反爬方面的技術(shù)并實(shí)踐后持續(xù)更新 } } return orginalMessage; }
比如后端返回的是323.14743.14743.1446,根據(jù)我們約定的算法,可以的到結(jié)果為1773
根據(jù) ttf 文件 Render 頁(yè)面
上面計(jì)算的到的1773,然后根據(jù)ttf文件,頁(yè)面看到的就是1995
然后為了防止爬蟲人員查看 JS 研究問題,所以對(duì) JS 的文件進(jìn)行了加密處理。如果你的技術(shù)棧是 Vue 、React 等,webpack 為你提供了 JS 加密的插件,也很方便處理
JS混淆工具
個(gè)人覺得這種方式還不是很安全。于是想到了各種方案的組合拳。比如
反爬升級(jí)版個(gè)人覺得如果一個(gè)前端經(jīng)驗(yàn)豐富的爬蟲開發(fā)者來說,上面的方案可能還是會(huì)存在被破解的可能,所以在之前的基礎(chǔ)上做了升級(jí)版本
組合拳1: 字體文件不要固定,雖然請(qǐng)求的鏈接是同一個(gè),但是根據(jù)當(dāng)前的時(shí)間戳的最后一個(gè)數(shù)字取模,比如 Demo 中對(duì)4取模,有4種值 0、1、2、3。這4種值對(duì)應(yīng)不同的字體文件,所以當(dāng)爬蟲絞盡腦汁爬到1種情況下的字體時(shí),沒想到再次請(qǐng)求,字體文件的規(guī)則變掉了
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101042.html
摘要:更詳細(xì)的內(nèi)容下一章開篇深入聊聊前后分離講述關(guān)于我目前在寫從零構(gòu)建前后分離項(xiàng)目系列,修正和補(bǔ)充以此為準(zhǔn)不斷更新的項(xiàng)目實(shí)踐地址彩蛋提前預(yù)覽下一章傳送門 開篇 : 縱觀WEB歷史演變 在校學(xué)習(xí)和幾年工作工作中不知不覺經(jīng)歷了一半的 WEB 歷史演變、對(duì)近幾年的發(fā)展比較了解,結(jié)合經(jīng)驗(yàn)聊聊 WEB 發(fā)展歷史。 演變不易,但也是必然,因?yàn)闉槿耸冀K要進(jìn)步。 WEB 的發(fā)展史 一、開山鼻祖 - 石器時(shí)代...
摘要:更詳細(xì)的內(nèi)容下一章開篇深入聊聊前后分離講述關(guān)于我目前在寫從零構(gòu)建前后分離項(xiàng)目系列,修正和補(bǔ)充以此為準(zhǔn)不斷更新的項(xiàng)目實(shí)踐地址彩蛋提前預(yù)覽下一章傳送門 開篇 : 縱觀WEB歷史演變 在校學(xué)習(xí)和幾年工作工作中不知不覺經(jīng)歷了一半的 WEB 歷史演變、對(duì)近幾年的發(fā)展比較了解,結(jié)合經(jīng)驗(yàn)聊聊 WEB 發(fā)展歷史。 演變不易,但也是必然,因?yàn)闉槿耸冀K要進(jìn)步。 WEB 的發(fā)展史 一、開山鼻祖 - 石器時(shí)代...
摘要:大局意識(shí)就是合理地優(yōu)先處理某些重要的小項(xiàng)目。對(duì)于所做事情的意義,我想到了美國(guó)西部大淘金的時(shí)代。我在美國(guó)時(shí)發(fā)現(xiàn)很多產(chǎn)業(yè)都只剩下幾個(gè)巨頭在競(jìng)爭(zhēng),而國(guó)內(nèi)各種類似領(lǐng)域有大量小公司的存在。 Integ 是 SegmentFault 的前端 Hacker,在本次訪談中貢獻(xiàn)了編程五年多的感悟與總結(jié)。Codes Dont Lie 這個(gè)標(biāo)題(并非 Hips Dont Lie)代表了 Integ 的誠(chéng)懇和...
摘要:前端性能優(yōu)化的涉及點(diǎn)從服務(wù)器到協(xié)議再到宿主環(huán)境本身都要有比較深刻的認(rèn)識(shí),業(yè)界目前主要還是以雅虎總結(jié)出來?xiàng)l前端性能優(yōu)化的黃金軍規(guī)為參考。 歡迎大家前往騰訊云技術(shù)社區(qū),獲取更多騰訊海量技術(shù)實(shí)踐干貨哦~ 導(dǎo)語(yǔ) : 從事前端有6年+的時(shí)間了,從最開始的美工到重構(gòu)再到偏向js邏輯開發(fā)的前端開發(fā),一直在前端這個(gè)行業(yè)里面摸索和學(xué)習(xí),我現(xiàn)在將自己這些年的一個(gè)心得體會(huì)來個(gè)系統(tǒng)性的梳理寫成一篇關(guān)于性能優(yōu)化...
閱讀 1991·2021-11-16 11:45
閱讀 3718·2021-09-06 15:02
閱讀 2048·2019-08-30 15:44
閱讀 2309·2019-08-30 11:21
閱讀 1879·2019-08-29 16:31
閱讀 3452·2019-08-29 13:55
閱讀 1926·2019-08-29 12:15
閱讀 3277·2019-08-28 18:05