大前端时代安全性如何作

以前在上家公司的时候作过一些爬虫的工做,也帮助爬虫工程师解决过一些问题。而后我写过一些文章发布到网上,以后有一些人就找我作一些爬虫的外包,内容大概是爬取小红书的用户数据和商品数据,可是我没作。我以为对于国内的大数据公司没几家是有真正的大数据量,而是经过爬虫工程师团队不断的去各地爬取数据,所以不要觉得咱们的数据没价值,对于内容型的公司来讲,数据是可信竞争力。那么我接下来想说的就是网络和数据的安全性问题。
对于内容型的公司,数据的安全性很重要。对于内容公司来讲,数据的重要性不言而喻。好比你一个作在线教育的平台,题目的数据很重要吧,可是被别人经过爬虫技术所有爬走了?若是核心竞争力都被拿走了,那就是凉凉。再比说有个独立开发者想抄袭你的产品,经过抓包和爬虫手段将你核心的数据拿走,而后短时间内作个网站和 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逆向、防重放、服务端等)。

相关文章
相关标签/搜索