大前端時代安全性如何作

以前在上家公司的時候作過一些爬蟲的工做,也幫助爬蟲工程師解決過一些問題。而後我寫過一些文章發佈到網上,以後有一些人就找我作一些爬蟲的外包,內容大概是爬取小紅書的用戶數據和商品數據,可是我沒作。我以爲對於國內的大數據公司沒幾家是有真正的大數據量,而是經過爬蟲工程師團隊不斷的去各地爬取數據,所以不要覺得咱們的數據沒價值,對於內容型的公司來講,數據是可信競爭力。那麼我接下來想說的就是網絡和數據的安全性問題。
對於內容型的公司,數據的安全性很重要。對於內容公司來講,數據的重要性不言而喻。好比你一個作在線教育的平臺,題目的數據很重要吧,可是被別人經過爬蟲技術所有爬走了?若是核心競爭力都被拿走了,那就是涼涼。再比說有個獨立開發者想抄襲你的產品,經過抓包和爬蟲手段將你核心的數據拿走,而後短時間內作個網站和 App,短時間內成爲你的勁敵。

背景

目前經過 App 中的 網頁分析後,咱們的數據安全性作的較差,有如下幾個點存在問題:javascript

  1. 網站的數據經過最先期的先後端分離來實現。稍微學過 Web 前端的工程師均可以經過神器 Chrome 分析網站,進而爬取須要的數據。打開 「Network」就能夠看到網站的全部網絡請求了,哎呀,不當心我看到了什麼?沒錯就是網站的接口信息均可以看到了。好比 「detail.json?itemId=141529859」。或者你的網站接口有些特殊的判斷處理,將一些信息存儲到 sessionStorage、cookie、localStorage 裏面,有點前端經驗的爬蟲工程師心想」嘿嘿嘿,這不是在裸奔數據麼「。或者有些參數是經過 JavaScript 臨時經過函數生成的。問題不大,工程師也能夠對網頁元素進行查找,找到關鍵的 id、或者 css 類名,而後在 "Search「 能夠進行查找,找到對應的代碼 JS 代碼,點擊查看代碼,若是是早期前端開發模式那麼代碼就是裸奔的,跟開發者在本身的 IDE 裏面看到的內容同樣,有經驗的爬蟲就能夠拿這個作事情,所以安全性問題亟待解決。

想知道 Chrome 更多的調試使用技巧,看看這篇文章css

Chrome調試1
Chrome調試2
Chrome調試3

  1. App 的數據即便採用了 HTTPS,可是對於專業的抓包工具也是能夠直接拿到數據的,所以 App 的安全問題也能夠作一些提升,具體的策略下文會講到。

想知道 Charles 的更多使用技巧,能夠看看這篇文章html

爬蟲手段

  • 目前爬蟲技術都是從渲染好的 html 頁面直接找到感興趣的節點,而後獲取對應的文本
  • 有些網站安全性作的好,好比列表頁可能好獲取,可是詳情頁就須要從列表頁點擊對應的 item,將 itemId 經過 form 表單提交,服務端生成對應的參數,而後重定向到詳情頁(重定向過來的地址後才帶有詳情頁的參數 detailID),這個步驟就能夠攔截掉一部分的爬蟲開發者

解決方案

制定出Web 端反爬技術方案

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

  • 使用HTTPS 協議
  • 單位時間內限制掉請求次數過多,則封鎖該帳號
  • 前端技術限制 (接下來是核心技術)
# 好比須要正確顯示的數據爲「19950220」

1. 先按照本身需求利用相應的規則(數字亂序映射,好比正常的0對應仍是0,可是亂序就是 0 <-> 1,1 <-> 9,3 <-> 8,...)製做自定義字體(ttf)
2. 根據上面的亂序映射規律,求獲得須要返回的數據 19950220 -> 17730220
3. 對於第一步獲得的字符串,依次遍歷每一個字符,將每一個字符根據按照線性變換(y=kx+b)。線性方程的係數和常數項是根據當前的日期計算獲得的。好比當前的日期爲「2018-07-24」,那麼線性變換的 k 爲 7,b 爲 24。
4. 而後將變換後的每一個字符串用「3.1415926」拼接返回給接口調用者。(爲何是3.1415926,由於對數字僞造反爬,因此拼接的文本確定是數字的話不太會引發研究者的注意,可是數字長度過短會誤傷正常的數據,因此用所熟悉的 Π)

​```
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
​```

# 前端拿到數據後再解密,解密後根據自定義的字體 Render 頁面
1. 先將拿到的字符串按照「3.1415926」拆分爲數組
2. 對數組的每1個數據,按照「線性變換」(y=kx+b,k和b一樣按照當前的日期求解獲得),逆向求解到本來的值。
3. 將步驟2的的到的數據依次拼接,再根據 ttf 文件 Render 頁面上。
  • 後端須要根據上一步設計的協議將數據進行加密處理

下面以 Node.js 爲例講解後端須要作的事情java

  • 首前後端設置接口路由
  • 獲取路由後面的參數
  • 根據業務須要根據 SQL 語句生成對應的數據。若是是數字部分,則須要按照上面約定的方法加以轉換。
  • 將生成數據轉換成 JSON 返回給調用者node

    // 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下的數據加密規則
        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下的數據加密規則
        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下的數據加密規則
        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 = "&#xefab;";
          }
          else if (rawNumber == 2) {
            mapData = "&#xeba3;";
          }
          else if (rawNumber == 3) {
            mapData = "&#xecfa;";
          }
          else if (rawNumber == 4) {
            mapData = "&#xedfd;";
          }
          else if (rawNumber == 5) {
            mapData = "&#xeffa;";
          }
          else if (rawNumber == 6) {
            mapData = "&#xef3a;";
          }
          else if (rawNumber == 7) {
            mapData = "&#xe6f5;";
          }
          else if (rawNumber == 8) {
            mapData = "&#xecb2;";
          }
          else if (rawNumber == 9) {
            mapData = "&#xe8ae;";
          }
          else if (rawNumber == 0) {
            mapData = "&#xe1f2;";
          }
        }
        else{
          mapData = rawNumber;
        }
      } else if (ruleTypeNumber == 4) {
        var sources = ["年", "萬", "業", "人", "信", "元", "千", "司", "州", "資", "造", "錢"];
        //判斷字符串爲漢字
        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 = "&#x" + 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("浙江省杭州市拱墅區石祥路",rule),
                    bidprice: LBPEncode("2萬元",rule),
                    negative: LBPEncode("2018年辦事效率過高、負面基本沒有",rule),
                    title: LBPEncode("建造師",rule),
                    honor: LBPEncode("最佳獎",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();
    };
  • 前端根據服務端返回的數據逆向解密webpack

    $("#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{
            //其餘狀況待續,本 Demo 根據本人在研究反爬方面的技術並實踐後持續更新
          }
      }
      return orginalMessage;
    }

好比後端返回的是323.14743.14743.1446,根據咱們約定的算法,能夠的到結果爲1773git

  • 根據 ttf 文件 Render 頁面
    自定義字體文件
    上面計算的到的1773,而後根據ttf文件,頁面看到的就是1995
  • 而後爲了防止爬蟲人員查看 JS 研究問題,因此對 JS 的文件進行了加密處理。若是你的技術棧是 Vue 、React 等,webpack 爲你提供了 JS 加密的插件,也很方便處理

    JS混淆工具github

  • 我的以爲這種方式還不是很安全。因而想到了各類方案的組合拳。好比

反爬升級版

我的以爲若是一個前端經驗豐富的爬蟲開發者來講,上面的方案可能仍是會存在被破解的可能,因此在以前的基礎上作了升級版本web

  1. 組合拳1: 字體文件不要固定,雖然請求的連接是同一個,可是根據當前的時間戳的最後一個數字取模,好比 Demo 中對4取模,有4種值 0、一、二、3。這4種值對應不一樣的字體文件,因此當爬蟲絞盡腦汁爬到1種狀況下的字體時,沒想到再次請求,字體文件的規則變掉了 😂
  2. 組合拳2: 前面的規則是字體問題亂序,可是隻是數字匹配打亂掉。好比 1 -> 4, 5 -> 8。接下來的套路就是每一個數字對應一個 unicode 碼 ,而後製做本身須要的字體,能夠是 .ttf、.woff 等等。

網頁檢察元素獲得的效果
接口返回數據

這幾種組合拳打下來。對於通常的爬蟲就放棄了。

反爬手段再升級

上面說的方法主要是針對數字作的反爬手段,若是要對漢字進行反爬怎麼辦?接下來提供幾種方案

  1. 方案1: 對於你站點頻率最高的詞雲,作一個漢字映射,也就是自定義字體文件,步驟跟數字同樣。先將經常使用的漢字生成對應的 ttf 文件;根據下面提供的連接,將 ttf 文件轉換爲 svg 文件,而後在下面的「字體映射」連接點進去的網站上面選擇前面生成的 svg 文件,將svg文件裏面的每一個漢字作個映射,也就是將漢字專爲 unicode 碼(注意這裏的 unicode 碼不要去在線直接生成,由於直接生成的東西也就是有規律的。我給的作法是先用網站生成,而後將獲得的結果作個簡單的變化,好比將「e342」轉換爲 「e231」);而後接口返回的數據按照咱們的這個字體文件的規則反過去映射出來。
  2. 方案2: 將網站的重要字體,將 html 部分生成圖片,這樣子爬蟲要識別到須要的內容成本就很高了,須要用到 OCR。效率也很低。因此能夠攔截掉一部分的爬蟲
  3. 方案3: 看到攜程的技術分享「反爬的最高境界就是 Canvas 的指紋,原理是不一樣的機器不一樣的硬件對於 Canvas 畫出的圖老是存在像素級別的偏差,所以咱們判斷當對於訪問來講大量的 canvas 的指紋一致的話,則認爲是爬蟲,則能夠封掉它」。

    本人將方案1實現到 Demo 中了。

關鍵步驟

  1. 先根據大家的產品找到經常使用的關鍵詞,生成詞雲
  2. 根據詞雲,將每一個字生成對應的 unicode 碼
  3. 將詞雲包括的漢字作成一個字體庫
  4. 將字體庫 .ttf 作成 svg 格式,而後上傳到 icomoon 製做自定義的字體,可是有規則,好比 「年」 對應的 unicode 碼「u5e74」 ,可是咱們須要作一個 愷撒加密 ,好比咱們設置 偏移量 爲1,那麼通過愷撒加密 「年」對應的 unicode 碼是「u5e75」 。利用這種規則製做咱們須要的字體庫
  5. 在每次調用接口的時候服務端作的事情是:服務端封裝某個方法,將數據通過方法判斷是否是在詞雲中,若是是詞雲中的字符,利用規則(找到漢字對應的 unicode 碼,再根據凱撒加密,設置對應的偏移量,Demo 中爲1,將每一個漢字加密處理)加密處理後返回數據
  6. 客戶端作的事情:

    • 先引入咱們前面製做好的漢字字體庫
    • 調用接口拿到數據,顯示到對應的 Dom 節點上
    • 若是是漢字文本,咱們將對應節點的 css 類設置成漢字類,該類對應的 font-family 是咱們上面引入的漢字字體庫
//style.css
@font-face {
  font-family: "NumberFont";
  src: url('http://127.0.0.1:8080/Util/analysis');
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

@font-face {
  font-family: "CharacterFont";
  src: url('http://127.0.0.1:8080/Util/map');
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h2 {
  font-family: "NumberFont";
}

h3,a{
  font-family: "CharacterFont";
}

接口效果
審查元素效果

傳送門

字體制做的步驟ttf轉svg字體映射規則

實現的效果

  1. 頁面上看到的數據跟審查元素看到的結果不一致
  2. 去查看接口數據跟審覈元素和界面看到的三者不一致
  3. 頁面每次刷新以前得出的結果更不一致
  4. 對於數字和漢字的處理手段都不一致

這幾種組合拳打下來。對於通常的爬蟲就放棄了。

數字反爬-網頁顯示效果、審查元素、接口結果狀況1
數字反爬-網頁顯示效果、審查元素、接口結果狀況2
數字反爬-網頁顯示效果、審查元素、接口結果狀況3
數字反爬-網頁顯示效果、審查元素、接口結果狀況4
漢字反爬-網頁顯示效果、審查元素、接口結果狀況1
漢字反爬-網頁顯示效果、審查元素、接口結果狀況2


前面的 ttf 轉 svg 網站當 ttf 文件太大會限制轉換,讓你購買,下面貼出個新的連接。

ttf轉svg

Demo 地址

效果演示

運行步驟

//客戶端。先查看本機 ip 在 Demo/Spider-develop/Solution/Solution1.js 和 Demo/Spider-develop/Solution/Solution2.js  裏面將接口地址修改成本機 ip

$ cd Demo
$ ls
REST        Spider-release    file-Server.js
Spider-develop    Util        rule.json
$ node file-Server.js 
Server is runnig at http://127.0.0.1:8080/

//服務端 先安裝依賴
$ cd REST/
$ npm install
$ node app.js

App 端安全的解決方案

  • 目前 App 的網絡通訊基本都是用 HTTPS 的服務,可是隨便一個抓包工具都是能夠看到 HTTPS 接口的詳細數據,爲了作到防止抓包和沒法模擬接口的狀況,咱們採起如下措施:

    1. 中間人盜用數據,咱們能夠採起 HTTPS 證書的雙向認證,這樣子實現的效果就是中間人在開啓抓包軟件分析 App 的網絡請求的時候,網絡會自動斷掉,沒法查看分析請求的狀況
    2. 對於防止用戶模仿咱們的請求再次發起請求,咱們能夠採用 「防重放策略」,用戶再也沒法模仿咱們的請求,再次去獲取數據了。
    3. 對於 App 內的 H5 資源,反爬蟲方案能夠採用上面的解決方案,H5 內部的網絡請求能夠經過 Hybrid 層讓 Native 的能力去完成網絡請求,完成以後將數據回調給 JS。這麼作的目的是每每咱們的 Native 層有完善的帳號體系和網絡層以及良好的安全策略、鑑權體系等等。
    4. 後期會討論 App 安全性的更深層次玩法,好比從逆向的角度出發如何保護 App 的安全性。提早給出一篇逆向安全方面的文章

關於 Hybrid 的更多內容,能夠看看這篇文章 Awesome Hybrid

  • 好比 JS 須要發起一個網絡請求,那麼按照上面將網絡請求讓 Native 去完成,而後回調給 JS

    JS 端代碼

    var requestObject = {
      url: arg.Api + "SearchInfo/getLawsInfo",
      params: requestparams,
      Hybrid_Request_Method: 0
    };
    requestHybrid({
      tagname: 'NativeRequest',
      param: requestObject,
      encryption: 1,
      callback: function (data) {
        renderUI(data);
      }
    })

    Native 代碼(iOS爲例)

    [self.bridge registerHandler:@"NativeRequest" handler:^(id data, WVJBResponseCallback responseCallback) {
          
        NSAssert([data isKindOfClass:[NSDictionary class]], @"H5 端不按套路");
        if ([data isKindOfClass:[NSDictionary class]]) {
            
            NSDictionary *dict = (NSDictionary *)data;
            RequestModel *requestModel = [RequestModel yy_modelWithJSON:dict];
            NSAssert( (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) || (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get ), @"H5 端不按套路");
            
            [HybridRequest requestWithNative:requestModel hybridRequestSuccess:^(id responseObject) {
                
                NSDictionary *json = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONReadingMutableLeaves error:nil];
                responseCallback([self convertToJsonData:@{@"success":@"1",@"data":json}]);
                
            } hybridRequestfail:^{
                
                LBPLog(@"H5 call Native`s request failed");
                responseCallback([self convertToJsonData:@{@"success":@"0",@"data":@""}]);
            }];
        }
    }];

以上是第一階段的安全性總結,後期應該會更新(App逆向、防重放、服務端等)。

相關文章
相關標籤/搜索