自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

大前端時(shí)代安全性如何做

安全 應(yīng)用安全
我覺得對(duì)于國(guó)內(nèi)的大數(shù)據(jù)公司沒幾家是有真正的大數(shù)據(jù)量,而是通過爬蟲工程師團(tuán)隊(duì)不斷的去各地爬取數(shù)據(jù),因此不要以為我們的數(shù)據(jù)沒價(jià)值,對(duì)于內(nèi)容型的公司來(lái)說,數(shù)據(jù)是可信競(jìng)爭(zhēng)力。那么我接下來(lái)想說的就是網(wǎng)絡(luò)和數(shù)據(jù)的安全性問題。

之前在上家公司的時(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)容型的公司來(lái)說,數(shù)據(jù)是可信競(jìng)爭(zhēng)力。那么我接下來(lái)想說的就是網(wǎng)絡(luò)和數(shù)據(jù)的安全性問題。

[[255777]]

對(duì)于內(nèi)容型的公司,數(shù)據(jù)的安全性很重要。對(duì)于內(nèi)容公司來(lá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ù)通過最早期的前后端分離來(lái)實(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è)做事情,因此安全性問題亟待解決。

 

 

App 的數(shù)據(jù)即使采用了 HTTPS,但是對(duì)于專業(yè)的抓包工具也是可以直接拿到數(shù)據(jù)的,因此 App 的安全問題也可以做一些提高,具體的策略下文會(huì)講到。

爬蟲手段

  • 目前爬蟲技術(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è)(重定向過來(lái)的地址后才帶有詳情頁(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ù)限制 (接下來(lái)是核心技術(shù))
  1. # 比如需要正確顯示的數(shù)據(jù)為“19950220” 
  2.  
  3. 1. 先按照自己需求利用相應(yīng)的規(guī)則(數(shù)字亂序映射,比如正常的0對(duì)應(yīng)還是0,但是亂序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)制作自定義字體(ttf) 
  4. 2. 根據(jù)上面的亂序映射規(guī)律,求得到需要返回的數(shù)據(jù) 19950220 -> 17730220 
  5. 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。 
  6. 4. 然后將變換后的每個(gè)字符串用“3.1415926”拼接返回給接口調(diào)用者。(為什么是3.1415926,因?yàn)閷?duì)數(shù)字偽造反爬,所以拼接的文本肯定是數(shù)字的話不太會(huì)引起研究者的注意,但是數(shù)字長(zhǎng)度太短會(huì)誤傷正常的數(shù)據(jù),所以用所熟悉的 Π) 
  7.  
  8. ​``` 
  9. 1773 -> “1*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “7*7+24” + “3.1415926” + “3*7+24” -> 313.1415926733.1415926733.141592645 
  10. 02 -> "0*7+24" + "3.1415926" + "2*7+24" -> 243.141592638 
  11. 20 -> "2*7+24" + "3.1415926" + "0*7+24" -> 383.141592624 
  12. ​``` 
  13.  
  14. # 前端拿到數(shù)據(jù)后再解密,解密后根據(jù)自定義的字體 Render 頁(yè)面 
  15. 1. 先將拿到的字符串按照“3.1415926”拆分為數(shù)組 
  16. 2. 對(duì)數(shù)組的每1個(gè)數(shù)據(jù),按照“線性變換”(y=kx+b,k和b同樣按照當(dāng)前的日期求解得到),逆向求解到原本的值。 
  17. 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)用者
  1. // json 
  2. var JoinOparatorSymbol = "3.1415926"
  3. function encode(rawData, ruleType) { 
  4.   if (!isNotEmptyStr(rawData)) { 
  5.     return ""
  6.   } 
  7.   var date = new Date(); 
  8.   var year = date.getFullYear(); 
  9.   var month = date.getMonth() + 1; 
  10.   var day = date.getDate(); 
  11.  
  12.   var encodeData = ""
  13.   for (var index = 0; index < rawData.length; index++) { 
  14.     var datacomponent = rawData[index]; 
  15.     if (!isNaN(datacomponent)) { 
  16.       if (ruleType < 3) { 
  17.         var currentNumber = rawDataMap(String(datacomponent), ruleType); 
  18.         encodeData += (currentNumber * month + day) + JoinOparatorSymbol; 
  19.       } 
  20.       else if (ruleType == 4) { 
  21.         encodeData += rawDataMap(String(datacomponent), ruleType); 
  22.       } 
  23.       else { 
  24.         encodeData += rawDataMap(String(datacomponent), ruleType) + JoinOparatorSymbol; 
  25.       } 
  26.     } 
  27.     else if (ruleType == 4) { 
  28.       encodeData += rawDataMap(String(datacomponent), ruleType); 
  29.     } 
  30.  
  31.   } 
  32.   if (encodeData.length >= JoinOparatorSymbol.length) { 
  33.     var lastTwoString = encodeData.substring(encodeData.length - JoinOparatorSymbol.length, encodeData.length); 
  34.     if (lastTwoString == JoinOparatorSymbol) { 
  35.       encodeData = encodeData.substring(0, encodeData.length - JoinOparatorSymbol.length); 
  36.     } 
  37.   } 
  1. //字體映射處理 
  2. function rawDataMap(rawData, ruleType) { 
  3.  
  4.   if (!isNotEmptyStr(rawData) || !isNotEmptyStr(ruleType)) { 
  5.     return
  6.   } 
  7.   var mapData; 
  8.   var rawNumber = parseInt(rawData); 
  9.   var ruleTypeNumber = parseInt(ruleType); 
  10.   if (!isNaN(rawData)) { 
  11.     lastNumberCategory = ruleTypeNumber; 
  12.     //字體文件1下的數(shù)據(jù)加密規(guī)則 
  13.     if (ruleTypeNumber == 1) { 
  14.       if (rawNumber == 1) { 
  15.         mapData = 1; 
  16.       } 
  17.       else if (rawNumber == 2) { 
  18.         mapData = 2; 
  19.       } 
  20.       else if (rawNumber == 3) { 
  21.         mapData = 4; 
  22.       } 
  23.       else if (rawNumber == 4) { 
  24.         mapData = 5; 
  25.       } 
  26.       else if (rawNumber == 5) { 
  27.         mapData = 3; 
  28.       } 
  29.       else if (rawNumber == 6) { 
  30.         mapData = 8; 
  31.       } 
  32.       else if (rawNumber == 7) { 
  33.         mapData = 6; 
  34.       } 
  35.       else if (rawNumber == 8) { 
  36.         mapData = 9; 
  37.       } 
  38.       else if (rawNumber == 9) { 
  39.         mapData = 7; 
  40.       } 
  41.       else if (rawNumber == 0) { 
  42.         mapData = 0; 
  43.       } 
  44.     } 
  45.     //字體文件2下的數(shù)據(jù)加密規(guī)則 
  46.     else if (ruleTypeNumber == 0) { 
  47.  
  48.       if (rawNumber == 1) { 
  49.         mapData = 4; 
  50.       } 
  51.       else if (rawNumber == 2) { 
  52.         mapData = 2; 
  53.       } 
  54.       else if (rawNumber == 3) { 
  55.         mapData = 3; 
  56.       } 
  57.       else if (rawNumber == 4) { 
  58.         mapData = 1; 
  59.       } 
  60.       else if (rawNumber == 5) { 
  61.         mapData = 8; 
  62.       } 
  63.       else if (rawNumber == 6) { 
  64.         mapData = 5; 
  65.       } 
  66.       else if (rawNumber == 7) { 
  67.         mapData = 6; 
  68.       } 
  69.       else if (rawNumber == 8) { 
  70.         mapData = 7; 
  71.       } 
  72.       else if (rawNumber == 9) { 
  73.         mapData = 9; 
  74.       } 
  75.       else if (rawNumber == 0) { 
  76.         mapData = 0; 
  77.       } 
  78.     } 
  79.     //字體文件3下的數(shù)據(jù)加密規(guī)則 
  80.     else if (ruleTypeNumber == 2) { 
  81.  
  82.       if (rawNumber == 1) { 
  83.         mapData = 6; 
  84.       } 
  85.       else if (rawNumber == 2) { 
  86.         mapData = 2; 
  87.       } 
  88.       else if (rawNumber == 3) { 
  89.         mapData = 1; 
  90.       } 
  91.       else if (rawNumber == 4) { 
  92.         mapData = 3; 
  93.       } 
  94.       else if (rawNumber == 5) { 
  95.         mapData = 4; 
  96.       } 
  97.       else if (rawNumber == 6) { 
  98.         mapData = 8; 
  99.       } 
  100.       else if (rawNumber == 7) { 
  101.         mapData = 3; 
  102.       } 
  103.       else if (rawNumber == 8) { 
  104.         mapData = 7; 
  105.       } 
  106.       else if (rawNumber == 9) { 
  107.         mapData = 9; 
  108.       } 
  109.       else if (rawNumber == 0) { 
  110.         mapData = 0; 
  111.       } 
  112.     } 
  113.     else if (ruleTypeNumber == 3) { 
  114.  
  115.       if (rawNumber == 1) { 
  116.         mapData = "&#xefab;"
  117.       } 
  118.       else if (rawNumber == 2) { 
  119.         mapData = "&#xeba3;"
  120.       } 
  121.       else if (rawNumber == 3) { 
  122.         mapData = "&#xecfa;"
  123.       } 
  124.       else if (rawNumber == 4) { 
  125.         mapData = "&#xedfd;"
  126.       } 
  127.       else if (rawNumber == 5) { 
  128.         mapData = "&#xeffa;"
  129.       } 
  130.       else if (rawNumber == 6) { 
  131.         mapData = "&#xef3a;"
  132.       } 
  133.       else if (rawNumber == 7) { 
  134.         mapData = "&#xe6f5;"
  135.       } 
  136.       else if (rawNumber == 8) { 
  137.         mapData = "&#xecb2;"
  138.       } 
  139.       else if (rawNumber == 9) { 
  140.         mapData = "&#xe8ae;"
  141.       } 
  142.       else if (rawNumber == 0) { 
  143.         mapData = "&#xe1f2;"
  144.       } 
  145.     } 
  146.     else
  147.       mapData = rawNumber; 
  148.     } 
  149.   } else if (ruleTypeNumber == 4) { 
  150.     var sources = ["年""萬(wàn)""業(yè)""人""信""元""千""司""州""資""造""錢"]; 
  151.     //判斷字符串為漢字 
  152.     if (/^[\u4e00-\u9fa5]*$/.test(rawData)) { 
  153.  
  154.       if (sources.indexOf(rawData) > -1) { 
  155.         var currentChineseHexcod = rawData.charCodeAt(0).toString(16); 
  156.         var lastCompoent; 
  157.         var mapComponetnt; 
  158.         var numbers = ["0""1""2""3""4""5""6""7""8""9"]; 
  159.         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"]; 
  160.  
  161.         if (currentChineseHexcod.length == 4) { 
  162.           lastCompoent = currentChineseHexcod.substr(3, 1); 
  163.           var locationInComponents = 0; 
  164.           if (/[0-9]/.test(lastCompoent)) { 
  165.             locationInComponents = numbers.indexOf(lastCompoent); 
  166.             mapComponetnt = numbers[(locationInComponents + 1) % 10]; 
  167.           } 
  168.           else if (/[a-z]/.test(lastCompoent)) { 
  169.             locationInComponents = characters.indexOf(lastCompoent); 
  170.             mapComponetnt = characters[(locationInComponents + 1) % 26]; 
  171.           } 
  172.           mapData = "&#x" + currentChineseHexcod.substr(0, 3) + mapComponetnt + ";"
  173.         } 
  174.       } else { 
  175.         mapData = rawData; 
  176.       } 
  177.  
  178.     } 
  179.     else if (/[0-9]/.test(rawData)) { 
  180.       mapData = rawDataMap(rawData, 2); 
  181.     } 
  182.     else { 
  183.       mapData = rawData; 
  184.     } 
  185.  
  186.   } 
  187.   return mapData; 
  1. //api 
  2. module.exports = { 
  3.     "GET /api/products": async (ctx, next) => { 
  4.         ctx.response.type = "application/json"
  5.         ctx.response.body = { 
  6.             products: products 
  7.         }; 
  8.     }, 
  9.  
  10.     "GET /api/solution1": async (ctx, next) => { 
  11.  
  12.         try { 
  13.             var data = fs.readFileSync(pathname, "utf-8"); 
  14.             ruleJson = JSON.parse(data); 
  15.             rule = ruleJson.data.rule
  16.         } catch (error) { 
  17.             console.log("fail: " + error); 
  18.         } 
  19.  
  20.         var data = { 
  21.             code: 200, 
  22.             message: "success"
  23.             data: { 
  24.                 name"@杭城小劉"
  25.                 year: LBPEncode("1995"rule), 
  26.                 month: LBPEncode("02"rule), 
  27.                 day: LBPEncode("20"rule), 
  28.                 analysis : rule 
  29.             } 
  30.         } 
  31.  
  32.         ctx.set("Access-Control-Allow-Origin""*"); 
  33.         ctx.response.type = "application/json"
  34.         ctx.response.body = data; 
  35.     }, 
  36.  
  37.  
  38.     "GET /api/solution2": async (ctx, next) => { 
  39.         try { 
  40.             var data = fs.readFileSync(pathname, "utf-8"); 
  41.             ruleJson = JSON.parse(data); 
  42.             rule = ruleJson.data.rule
  43.         } catch (error) { 
  44.             console.log("fail: " + error); 
  45.         } 
  46.  
  47.         var data = { 
  48.             code: 200, 
  49.             message: "success"
  50.             data: { 
  51.                 name: LBPEncode("建造師",rule), 
  52.                 birthday: LBPEncode("1995年02月20日",rule), 
  53.                 company: LBPEncode("中天公司",rule), 
  54.                 address: LBPEncode("浙江省杭州市拱墅區(qū)石祥路",rule), 
  55.                 bidprice: LBPEncode("2萬(wàn)元",rule), 
  56.                 negative: LBPEncode("2018年辦事效率太高、負(fù)面基本沒有",rule), 
  57.                 title: LBPEncode("建造師",rule), 
  58.                 honor: LBPEncode("最佳獎(jiǎng)",rule), 
  59.                 analysis : rule 
  60.             } 
  61.         } 
  62.         ctx.set("Access-Control-Allow-Origin""*"); 
  63.         ctx.response.type = "application/json"
  64.         ctx.response.body = data; 
  65.     }, 
  66.  
  67.     "POST /api/products": async (ctx, next) => { 
  68.         var p = { 
  69.             name: ctx.request.body.name
  70.             price: ctx.request.body.price 
  71.         }; 
  72.         products.push(p); 
  73.         ctx.response.type = "application/json"
  74.         ctx.response.body = p; 
  75.     } 
  76. }; 
  1. //路由 
  2. const fs = require("fs"); 
  3.  
  4. function addMapping(router, mapping){ 
  5.     for(var url in mapping){ 
  6.         if (url.startsWith("GET")) { 
  7.             var path = url.substring(4); 
  8.             router.get(path,mapping[url]); 
  9.             console.log(`Register URL mapping: GET: ${path}`); 
  10.         }else if (url.startsWith('POST ')) { 
  11.             var path = url.substring(5); 
  12.             router.post(path, mapping[url]); 
  13.             console.log(`Register URL mapping: POST ${path}`); 
  14.         } else if (url.startsWith('PUT ')) { 
  15.             var path = url.substring(4); 
  16.             router.put(path, mapping[url]); 
  17.             console.log(`Register URL mapping: PUT ${path}`); 
  18.         } else if (url.startsWith('DELETE ')) { 
  19.             var path = url.substring(7); 
  20.             router.del(path, mapping[url]); 
  21.             console.log(`Register URL mapping: DELETE ${path}`); 
  22.         } else { 
  23.             console.log(`Invalid URL: ${url}`); 
  24.         } 
  25.  
  26.     } 
  27.  
  28.  
  29. function addControllers(router, dir){ 
  30.     fs.readdirSync(__dirname + "/" + dir).filter( (f) => { 
  31.         return f.endsWith(".js"); 
  32.     }).forEach( (f) => { 
  33.         console.log(`Process controllers:${f}...`); 
  34.         let mapping = require(__dirname + "/" + dir + "/" + f); 
  35.         addMapping(router,mapping); 
  36.     }); 
  37.  
  38. module.exports = function(dir){ 
  39.     let controllers = dir || "controller"
  40.     let router = require("koa-router")(); 
  41.  
  42.     addControllers(router,controllers); 
  43.     return router.routes(); 
  44. }; 
  • 前端根據(jù)服務(wù)端返回的數(shù)據(jù)逆向解密
  1. $("#year").html(getRawData(data.year,log)); 
  2.  
  3. // util.js 
  4. var JoinOparatorSymbol = "3.1415926"
  5. function isNotEmptyStr($str) { 
  6.   if (String($str) == "" || $str == undefined || $str == null || $str == "null") { 
  7.     return false
  8.   } 
  9.   return true
  10.  
  11. function getRawData($json,analisys) { 
  12.   $json = $json.toString(); 
  13.   if (!isNotEmptyStr($json)) { 
  14.     return
  15.   } 
  16.    
  17.   var date= new Date(); 
  18.   var year = date.getFullYear(); 
  19.   var month = date.getMonth() + 1; 
  20.   var day = date.getDate(); 
  21.   var datacomponents = $json.split(JoinOparatorSymbol); 
  22.   var orginalMessage = ""
  23.   for(var index = 0;index < datacomponents.length;index++){ 
  24.     var datacomponent = datacomponents[index]; 
  25.       if (!isNaN(datacomponent) && analisys < 3){ 
  26.           var currentNumber = parseInt(datacomponent); 
  27.           orginalMessage += (currentNumber -  day)/month
  28.       } 
  29.       else if(analisys == 3){ 
  30.          orginalMessage += datacomponent; 
  31.       } 
  32.       else
  33.         //其他情況待續(xù),本 Demo 根據(jù)本人在研究反爬方面的技術(shù)并實(shí)踐后持續(xù)更新 
  34.       } 
  35.   } 
  36.   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ā)者來(lái)說,上面的方案可能還是會(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ī)則變掉了 😂
  • 組合拳2: 前面的規(guī)則是字體問題亂序,但是只是數(shù)字匹配打亂掉。比如 1 -> 4, 5 -> 8。接下來(lái)的套路就是每個(gè)數(shù)字對(duì)應(yīng)一個(gè) unicode 碼 ,然后制作自己需要的字體,可以是 .ttf、.woff 等等。

這幾種組合拳打下來(lái)。對(duì)于一般的爬蟲就放棄了。

反爬手段再升級(jí)

上面說的方法主要是針對(duì)數(shù)字做的反爬手段,如果要對(duì)漢字進(jìn)行反爬怎么辦?接下來(lái)提供幾種方案

  • 方案1: 對(duì)于你站點(diǎn)頻率最高的詞云,做一個(gè)漢字映射,也就是自定義字體文件,步驟跟數(shù)字一樣。先將常用的漢字生成對(duì)應(yīng)的 ttf 文件;根據(jù)下面提供的鏈接,將 ttf 文件轉(zhuǎn)換為 svg 文件,然后在下面的“字體映射”鏈接點(diǎn)進(jìn)去的網(wǎng)站上面選擇前面生成的 svg 文件,將svg文件里面的每個(gè)漢字做個(gè)映射,也就是將漢字專為 unicode 碼(注意這里的 unicode 碼不要去在線直接生成,因?yàn)橹苯由傻臇|西也就是有規(guī)律的。我給的做法是先用網(wǎng)站生成,然后將得到的結(jié)果做個(gè)簡(jiǎn)單的變化,比如將“e342”轉(zhuǎn)換為 “e231”);然后接口返回的數(shù)據(jù)按照我們的這個(gè)字體文件的規(guī)則反過去映射出來(lái)。
  • 方案2: 將網(wǎng)站的重要字體,將 html 部分生成圖片,這樣子爬蟲要識(shí)別到需要的內(nèi)容成本就很高了,需要用到 OCR。效率也很低。所以可以攔截掉一部分的爬蟲
  • 方案3: 看到攜程的技術(shù)分享“反爬的最高境界就是 Canvas 的指紋,原理是不同的機(jī)器不同的硬件對(duì)于 Canvas 畫出的圖總是存在像素級(jí)別的誤差,因此我們判斷當(dāng)對(duì)于訪問來(lái)說大量的 canvas 的指紋一致的話,則認(rèn)為是爬蟲,則可以封掉它”。

本人將方案1實(shí)現(xiàn)到 Demo 中了。

關(guān)鍵步驟

  1. 先根據(jù)你們的產(chǎn)品找到常用的關(guān)鍵詞,生成詞云
  2. 根據(jù)詞云,將每個(gè)字生成對(duì)應(yīng)的 unicode 碼
  3. 將詞云包括的漢字做成一個(gè)字體庫(kù)
  4. 將字體庫(kù) .ttf 做成 svg 格式,然后上傳到 icomoon 制作自定義的字體,但是有規(guī)則,比如 “年” 對(duì)應(yīng)的 unicode 碼是 “u5e74” ,但是我們需要做一個(gè) 愷撒加密 ,比如我們?cè)O(shè)置 偏移量 為1,那么經(jīng)過愷撒加密 “年”對(duì)應(yīng)的 unicode 碼是“u5e75” 。利用這種規(guī)則制作我們需要的字體庫(kù)
  5. 在每次調(diào)用接口的時(shí)候服務(wù)端做的事情是:服務(wù)端封裝某個(gè)方法,將數(shù)據(jù)經(jīng)過方法判斷是不是在詞云中,如果是詞云中的字符,利用規(guī)則(找到漢字對(duì)應(yīng)的 unicode 碼,再根據(jù)凱撒加密,設(shè)置對(duì)應(yīng)的偏移量,Demo 中為1,將每個(gè)漢字加密處理)加密處理后返回?cái)?shù)據(jù)
  6. 客戶端做的事情:
  • 先引入我們前面制作好的漢字字體庫(kù)
  • 調(diào)用接口拿到數(shù)據(jù),顯示到對(duì)應(yīng)的 Dom 節(jié)點(diǎn)上
  • 如果是漢字文本,我們將對(duì)應(yīng)節(jié)點(diǎn)的 css 類設(shè)置成漢字類,該類對(duì)應(yīng)的 font-family 是我們上面引入的漢字字體庫(kù)
  1. //style.css 
  2. @font-face { 
  3.   font-family: "NumberFont"
  4.   src: url('http://127.0.0.1:8080/Util/analysis'); 
  5.   -webkit-font-smoothing: antialiased; 
  6.   -moz-osx-font-smoothing: grayscale; 
  7.  
  8. @font-face { 
  9.   font-family: "CharacterFont"
  10.   src: url('http://127.0.0.1:8080/Util/map'); 
  11.   -webkit-font-smoothing: antialiased; 
  12.   -moz-osx-font-smoothing: grayscale; 
  13.  
  14. h2 { 
  15.   font-family: "NumberFont"
  16.  
  17. h3,a{ 
  18.   font-family: "CharacterFont"

 

 

傳送門

字體制作的步驟、ttf轉(zhuǎn)svg、字體映射規(guī)則

實(shí)現(xiàn)的效果

頁(yè)面上看到的數(shù)據(jù)跟審查元素看到的結(jié)果不一致

去查看接口數(shù)據(jù)跟審核元素和界面看到的三者不一致

頁(yè)面每次刷新之前得出的結(jié)果更不一致

對(duì)于數(shù)字和漢字的處理手段都不一致

這幾種組合拳打下來(lái)。對(duì)于一般的爬蟲就放棄了。

前面的 ttf 轉(zhuǎn) svg 網(wǎng)站當(dāng) ttf 文件太大會(huì)限制轉(zhuǎn)換,讓你購(gòu)買,下面貼出個(gè)新的鏈接。

ttf轉(zhuǎn)svg

Demo 地址

運(yùn)行步驟

  1. //客戶端。先查看本機(jī) ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js  里面將接口地址修改為本機(jī) ip 
  2.  
  3. $ cd Demo 
  4. $ ls 
  5. REST        Spider-release    file-Server.js 
  6. Spider-develop    Util        rule.json 
  7. $ node file-Server.js  
  8. Server is runnig at http://127.0.0.1:8080/ 
  9.  
  10. //服務(wù)端 先安裝依賴 
  11. $ cd REST/ 
  12. $ npm install 
  13. $ node app.js  

App 端安全的解決方案

  • 目前 App 的網(wǎng)絡(luò)通信基本都是用 HTTPS 的服務(wù),但是隨便一個(gè)抓包工具都是可以看到 HTTPS 接口的詳細(xì)數(shù)據(jù),為了做到防止抓包和無(wú)法模擬接口的情況,我們采取以下措施:
  1. 中間人盜用數(shù)據(jù),我們可以采取 HTTPS 證書的雙向認(rèn)證,這樣子實(shí)現(xiàn)的效果就是中間人在開啟抓包軟件分析 App 的網(wǎng)絡(luò)請(qǐng)求的時(shí)候,網(wǎng)絡(luò)會(huì)自動(dòng)斷掉,無(wú)法查看分析請(qǐng)求的情況
  2. 對(duì)于防止用戶模仿我們的請(qǐng)求再次發(fā)起請(qǐng)求,我們可以采用 「防重放策略」,用戶再也無(wú)法模仿我們的請(qǐng)求,再次去獲取數(shù)據(jù)了。
  3. 對(duì)于 App 內(nèi)的 H5 資源,反爬蟲方案可以采用上面的解決方案,H5 內(nèi)部的網(wǎng)絡(luò)請(qǐng)求可以通過 Hybrid 層讓 Native 的能力去完成網(wǎng)絡(luò)請(qǐng)求,完成之后將數(shù)據(jù)回調(diào)給 JS。這么做的目的是往往我們的 Native 層有完善的賬號(hào)體系和網(wǎng)絡(luò)層以及良好的安全策略、鑒權(quán)體系等等。
  4. 后期會(huì)討論 App 安全性的更深層次玩法,比如從逆向的角度出發(fā)如何保護(hù) App 的安全性。

關(guān)于 Hybrid 的更多內(nèi)容,可以看看這篇文章 Awesome Hybrid

  • 比如 JS 需要發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求,那么按照上面將網(wǎng)絡(luò)請(qǐng)求讓 Native 去完成,然后回調(diào)給 JS
  1. var requestObject = { 
  2.   url: arg.Api + "SearchInfo/getLawsInfo"
  3.   params: requestparams, 
  4.   Hybrid_Request_Method: 0 
  5. }; 
  6. requestHybrid({ 
  7.   tagname: 'NativeRequest'
  8.   param: requestObject, 
  9.   encryption: 1, 
  10.   callback: function (data) { 
  11.     renderUI(data); 
  12.   } 
  13. }) 

Native 代碼(iOS為例)

  1. [self.bridge registerHandler:@"NativeRequest" handler:^(id data, WVJBResponseCallback responseCallback) { 
  2.        
  3.     NSAssert([data isKindOfClass:[NSDictionary class]], @"H5 端不按套路"); 
  4.     if ([data isKindOfClass:[NSDictionary class]]) { 
  5.          
  6.         NSDictionary *dict = (NSDictionary *)data; 
  7.         RequestModel *requestModel = [RequestModel yy_modelWithJSON:dict]; 
  8.         NSAssert( (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) || (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get ), @"H5 端不按套路"); 
  9.          
  10.         [HybridRequest requestWithNative:requestModel hybridRequestSuccess:^(id responseObject) { 
  11.              
  12.             NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil]; 
  13.             responseCallback([self convertToJsonData:@{@"success":@"1",@"data":json}]); 
  14.              
  15.         } hybridRequestfail:^{ 
  16.              
  17.             LBPLog(@"H5 call Native`s request failed"); 
  18.             responseCallback([self convertToJsonData:@{@"success":@"0",@"data":@""}]); 
  19.         }]; 
  20.     } 
  21. }]; 

 

責(zé)任編輯:武曉燕 來(lái)源: segmentfault
相關(guān)推薦

2022-10-19 14:16:18

樣式隔離前綴css

2009-11-30 09:41:38

2024-05-28 09:05:31

2021-02-26 20:07:54

安全性健壯性代碼

2010-09-29 09:48:12

數(shù)據(jù)庫(kù)安全

2021-10-19 06:05:20

網(wǎng)站安全網(wǎng)絡(luò)威脅網(wǎng)絡(luò)攻擊

2022-10-10 13:22:38

物聯(lián)網(wǎng)安全隱私

2022-03-03 12:53:40

云遷移云計(jì)算云平臺(tái)

2017-06-12 08:47:14

ESXi安全vSphere

2020-03-06 10:36:21

JavaScriptCSSHTML

2021-03-15 14:59:28

物聯(lián)網(wǎng)互聯(lián)網(wǎng)IoT

2010-09-06 10:47:56

2022-03-10 14:17:11

區(qū)塊鏈數(shù)據(jù)安全技術(shù)

2012-07-30 10:07:01

2023-02-24 11:42:35

2017-12-08 21:26:52

物聯(lián)網(wǎng)DDI安全性

2009-06-26 13:35:50

安全審計(jì)信息安全吉大正元

2011-10-11 09:13:15

2012-05-14 11:39:58

2024-04-11 09:45:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)