日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
大前端時代安全性如何做

之前在上家公司的時候做過一些爬蟲的工作,也幫助爬蟲工程師解決過一些問題。然后我寫過一些文章發(fā)布到網(wǎng)上,之后有一些人就找我做一些爬蟲的外包,內(nèi)容大概是爬取小紅書的用戶數(shù)據(jù)和商品數(shù)據(jù),但是我沒做。我覺得對于國內(nèi)的大數(shù)據(jù)公司沒幾家是有真正的大數(shù)據(jù)量,而是通過爬蟲工程師團隊不斷的去各地爬取數(shù)據(jù),因此不要以為我們的數(shù)據(jù)沒價值,對于內(nèi)容型的公司來說,數(shù)據(jù)是可信競爭力。那么我接下來想說的就是網(wǎng)絡和數(shù)據(jù)的安全性問題。

成都創(chuàng)新互聯(lián)專注于鎮(zhèn)海企業(yè)網(wǎng)站建設,響應式網(wǎng)站設計,電子商務商城網(wǎng)站建設。鎮(zhèn)海網(wǎng)站建設公司,為鎮(zhèn)海等地區(qū)提供建站服務。全流程定制網(wǎng)站建設,專業(yè)設計,全程項目跟蹤,成都創(chuàng)新互聯(lián)專業(yè)和態(tài)度為您提供的服務

對于內(nèi)容型的公司,數(shù)據(jù)的安全性很重要。對于內(nèi)容公司來說,數(shù)據(jù)的重要性不言而喻。比如你一個做在線教育的平臺,題目的數(shù)據(jù)很重要吧,但是被別人通過爬蟲技術(shù)全部爬走了?如果核心競爭力都被拿走了,那就是涼涼。再比說有個獨立開發(fā)者想抄襲你的產(chǎn)品,通過抓包和爬蟲手段將你核心的數(shù)據(jù)拿走,然后短期內(nèi)做個網(wǎng)站和 App,短期內(nèi)成為你的勁敵。

背景

目前通過 App 中的 網(wǎng)頁分析后,我們的數(shù)據(jù)安全性做的較差,有以下幾個點存在問題:

網(wǎng)站的數(shù)據(jù)通過最早期的前后端分離來實現(xiàn)。稍微學過 Web 前端的工程師都可以通過神器 Chrome 分析網(wǎng)站,進而爬取需要的數(shù)據(jù)。打開 「Network」就可以看到網(wǎng)站的所有網(wǎng)絡請求了,哎呀,不小心我看到了什么?沒錯就是網(wǎng)站的接口信息都可以看到了。比如 “detail.json?itemId=141529859”。或者你的網(wǎng)站接口有些特殊的判斷處理,將一些信息存儲到 sessionStorage、cookie、localStorage 里面,有點前端經(jīng)驗的爬蟲工程師心想”嘿嘿嘿,這不是在裸奔數(shù)據(jù)么“?;蛘哂行﹨?shù)是通過 JavaScript 臨時通過函數(shù)生成的。問題不大,工程師也可以對網(wǎng)頁元素進行查找,找到關(guān)鍵的 id、或者 css 類名,然后在 "Search“ 可以進行查找,找到對應的代碼 JS 代碼,點擊查看代碼,如果是早期前端開發(fā)模式那么代碼就是裸奔的,跟開發(fā)者在自己的 IDE 里面看到的內(nèi)容一樣,有經(jīng)驗的爬蟲就可以拿這個做事情,因此安全性問題亟待解決。

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

爬蟲手段

  • 目前爬蟲技術(shù)都是從渲染好的 html 頁面直接找到感興趣的節(jié)點,然后獲取對應的文本
  • 有些網(wǎng)站安全性做的好,比如列表頁可能好獲取,但是詳情頁就需要從列表頁點擊對應的 item,將 itemId 通過 form 表單提交,服務端生成對應的參數(shù),然后重定向到詳情頁(重定向過來的地址后才帶有詳情頁的參數(shù) detailID),這個步驟就可以攔截掉一部分的爬蟲開發(fā)者

解決方案

制定出Web 端反爬技術(shù)方案

本人從這2個角度(網(wǎng)頁所見非所得、查接口請求沒用)出發(fā),制定了下面的反爬方案。

  • 使用HTTPS 協(xié)議
  • 單位時間內(nèi)限制掉請求次數(shù)過多,則封鎖該賬號
  • 前端技術(shù)限制 (接下來是核心技術(shù))
 
 
 
 
  1. # 比如需要正確顯示的數(shù)據(jù)為“19950220” 
  2.  
  3. 1. 先按照自己需求利用相應的規(guī)則(數(shù)字亂序映射,比如正常的0對應還是0,但是亂序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)制作自定義字體(ttf) 
  4. 2. 根據(jù)上面的亂序映射規(guī)律,求得到需要返回的數(shù)據(jù) 19950220 -> 17730220 
  5. 3. 對于第一步得到的字符串,依次遍歷每個字符,將每個字符根據(jù)按照線性變換(y=kx+b)。線性方程的系數(shù)和常數(shù)項是根據(jù)當前的日期計算得到的。比如當前的日期為“2018-07-24”,那么線性變換的 k 為 7,b 為 24。 
  6. 4. 然后將變換后的每個字符串用“3.1415926”拼接返回給接口調(diào)用者。(為什么是3.1415926,因為對數(shù)字偽造反爬,所以拼接的文本肯定是數(shù)字的話不太會引起研究者的注意,但是數(shù)字長度太短會誤傷正常的數(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 頁面 
  15. 1. 先將拿到的字符串按照“3.1415926”拆分為數(shù)組 
  16. 2. 對數(shù)組的每1個數(shù)據(jù),按照“線性變換”(y=kx+b,k和b同樣按照當前的日期求解得到),逆向求解到原本的值。 
  17. 3. 將步驟2的的到的數(shù)據(jù)依次拼接,再根據(jù) ttf 文件 Render 頁面上。 
  • 后端需要根據(jù)上一步設計的協(xié)議將數(shù)據(jù)進行加密處理

下面以 Node.js 為例講解后端需要做的事情

  • 首先后端設置接口路由
  • 獲取路由后面的參數(shù)
  • 根據(jù)業(yè)務需要根據(jù) SQL 語句生成對應的數(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 = ""; 
  117.       } 
  118.       else if (rawNumber == 2) { 
  119.         mapData = ""; 
  120.       } 
  121.       else if (rawNumber == 3) { 
  122.         mapData = ""; 
  123.       } 
  124.       else if (rawNumber == 4) { 
  125.         mapData = ""; 
  126.       } 
  127.       else if (rawNumber == 5) { 
  128.         mapData = ""; 
  129.       } 
  130.       else if (rawNumber == 6) { 
  131.         mapData = ""; 
  132.       } 
  133.       else if (rawNumber == 7) { 
  134.         mapData = ""; 
  135.       } 
  136.       else if (rawNumber == 8) { 
  137.         mapData = ""; 
  138.       } 
  139.       else if (rawNumber == 9) { 
  140.         mapData = ""; 
  141.       } 
  142.       else if (rawNumber == 0) { 
  143.         mapData = ""; 
  144.       } 
  145.     } 
  146.     else{ 
  147.       mapData = rawNumber; 
  148.     } 
  149.   } else if (ruleTypeNumber == 4) { 
  150.     var sources = ["年", "萬", "業(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萬元",rule), 
  56.                 negative: LBPEncode("2018年辦事效率太高、負面基本沒有",rule), 
  57.                 title: LBPEncode("建造師",rule), 
  58.                 honor: LBPEncode("最佳獎",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ù)服務端返回的數(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ù)并實踐后持續(xù)更新 
  34.       } 
  35.   } 
  36.   return orginalMessage; 

比如后端返回的是323.14743.14743.1446,根據(jù)我們約定的算法,可以的到結(jié)果為1773

  • 根據(jù) ttf 文件 Render 頁面

上面計算的到的1773,然后根據(jù)ttf文件,頁面看到的就是1995

  • 然后為了防止爬蟲人員查看 JS 研究問題,所以對 JS 的文件進行了加密處理。如果你的技術(shù)棧是 Vue 、React 等,webpack 為你提供了 JS 加密的插件,也很方便處理

JS混淆工具

個人覺得這種方式還不是很安全。于是想到了各種方案的組合拳。比如

反爬升級版

個人覺得如果一個前端經(jīng)驗豐富的爬蟲開發(fā)者來說,上面的方案可能還是會存在被破解的可能,所以在之前的基礎上做了升級版本

  • 組合拳1: 字體文件不要固定,雖然請求的鏈接是同一個,但是根據(jù)當前的時間戳的最后一個數(shù)字取模,比如 Demo 中對4取模,有4種值 0、1、2、3。這4種值對應不同的字體文件,所以當爬蟲絞盡腦汁爬到1種情況下的字體時,沒想到再次請求,字體文件的規(guī)則變掉了
  • 組合拳2: 前面的規(guī)則是字體問題亂序,但是只是數(shù)字匹配打亂掉。比如 1 -> 4, 5 -> 8。接下來的套路就是每個數(shù)字對應一個 unicode 碼 ,然后制作自己需要的字體,可以是 .ttf、.woff 等等。

這幾種組合拳打下來。對于一般的爬蟲就放棄了。

反爬手段再升級

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

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

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

關(guān)鍵步驟

  1. 先根據(jù)你們的產(chǎn)品找到常用的關(guān)鍵詞,生成詞云
  2. 根據(jù)詞云,將每個字生成對應的 unicode 碼
  3. 將詞云包括的漢字做成一個字體庫
  4. 將字體庫 .ttf 做成 svg 格式,然后上傳到 icomoon 制作自定義的字體,但是有規(guī)則,比如 “年” 對應的 unicode 碼是 “u5e74” ,但是我們需要做一個 愷撒加密 ,比如我們設置 偏移量 為1,那么經(jīng)過愷撒加密 “年”對應的 unicode 碼是“u5e75” 。利用這種規(guī)則制作我們需要的字體庫
  5. 在每次調(diào)用接口的時候服務端做的事情是:服務端封裝某個方法,將數(shù)據(jù)經(jīng)過方法判斷是不是在詞云中,如果是詞云中的字符,利用規(guī)則(找到漢字對應的 unicode 碼,再根據(jù)凱撒加密,設置對應的偏移量,Demo 中為1,將每個漢字加密處理)加密處理后返回數(shù)據(jù)
  6. 客戶端做的事情:
  • 先引入我們前面制作好的漢字字體庫
  • 調(diào)用接口拿到數(shù)據(jù),顯示到對應的 Dom 節(jié)點上
  • 如果是漢字文本,我們將對應節(jié)點的 css 類設置成漢字類,該類對應的 font-family 是我們上面引入的漢字字體庫
 
 
 
 
  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ī)則

實現(xiàn)的效果

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

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

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

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

這幾種組合拳打下來。對于一般的爬蟲就放棄了。

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

ttf轉(zhuǎn)svg

Demo 地址

運行步驟

 
 
 
 
  1. //客戶端。先查看本機 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js  里面將接口地址修改為本機 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. //服務端 先安裝依賴 
  11. $ cd REST/ 
  12. $ npm install 
  13. $ node app.js  

App 端安全的解決方案

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

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

  • 比如 JS 需要發(fā)起一個網(wǎng)絡請求,那么按照上面將網(wǎng)絡請求讓 Native 去完成,然后回調(diào)給 JS
 
 
 
 
  1. var requestObject = { 
  2.   url: arg.Api + "SearchInfo/getLawsInfo", 
  3.   params: requestparams, 
  4.   Hybrid_Request_Method: 0  文章名稱:大前端時代安全性如何做
    網(wǎng)頁網(wǎng)址:http://www.5511xx.com/article/cdcgpje.html