「bug信息收集系統」進階及源碼解析

內容簡介

加班到搖擺

上一章咱們介紹了「bug信息收集系統」的搭建和基本使用,本章將介紹它的高級用法,並對源碼進行解析。主要分爲如下三個部分:html

  • 數據安全問題,如何保證上報數據的安全性?
  • front-tool的擴展功能
  • front-tool的源碼解析
  • 因爲「前端報錯信息收集」功能還在開發中,這裏暫不做介紹

數據安全問題(leanCould中的權限管理)

問題所在

咱們先來明確目前遇到的問題前端

  • 數據不安全是因爲leanCloud的配置信息(APP ID,APP KEY)是經過明文保存到前端代碼中的。
  • 即便代碼進行了混淆,但使用過leanCloud的同窗仍是能夠很容易從中找到配置信息。
  • 獲取配置信息後,經過leanCloud提供的JS方法就能夠獲取上報的數據內容。形成用戶信息的泄漏。

leanCloud的配置信息是不可避免會被泄漏的(即便經過後端返回,黑客也能夠查看接口拿到配置信息),那咱們怎麼在配置信息不安全的狀況下,仍保持數據的安全性,不讓黑客獲取咱們上報的數據呢?vue

加班到搖擺

解決方法

咱們能夠經過leanCould的訪問控制列表(ACL 機制)(Access Control List)來實現
其原理經過在leanCloud中設置不一樣的角色,併爲不一樣的角色設置不一樣的權限,來約束其行爲。具體流程以下:ios

  • 設置兩個角色,role_write,role_read。
  • 爲role_write角色僅分配寫權限(再明確地說,是新增數據項的權限)。
  • role_read角色分配讀權限,及刪除權限。
  • 在front-tool組件中使用role_write角色登陸。
  • 在Bug的管理系統(bug-list,bug-detail)中使用role_read角色登陸。

這樣即便被黑客獲取了role_write角色的配置信息,也僅能添加新數據,不能修改,更不能查看。
而role_read角色是對於內部人員使用的,沒有信息安全問題。從而解決數據安全問題。ajax

加班到搖擺

具體配置方法以下

第一步,調用leanCloud方法添加兩個用戶角色。axios

  • 注意:這裏須要將角色的帳號密碼自行記錄起來,由於密碼是不會明文保存到leanCloud上的,要找回來比較麻煩,自行保存比較好。
  • 角色註冊完成後,進入以前建立的應用,找到系統默認建立的_User類,點擊
  • 右側展現的,就是咱們剛建立的兩個角色後端

    <script src="https://cdn1.lncld.net/static/js/av-min-1.2.1.js"></script>
    <script>
    AV.init({
      appId: 'xxx',
      appKey: 'xxx',
    });
    
    // 註冊角色
    function signup() {
      var user = new AV.User();
      user.setUsername('role_write');
      user.setPassword('xxx');
      user.signUp().then(function (loggedInUser) {
        alert('註冊成功')
        console.log(loggedInUser);
      });
    }
    </script>

第二步,關閉leanCloud使用js添加角色的權限api

  • 不知道你們發現沒有,這上面有一個坑,建立角色僅僅使用配置信息便可完成。
  • 黑客拿到配置信息後,建立新角色,就能夠繞過或破壞(如刪除角色)咱們的角色權限機制。
  • 因此這裏須要在leanCloud後臺修改配置,禁止經過JS進行角色的相關操做。
  • 一樣找到_User,點擊其餘,找到「權限設置」選項,將裏面全部的權限都設置「指定用戶」,即沒有角色對其進行修改。
  • 這樣就可關閉_User類的操做功能,經過JS代碼沒法對其進行修改。

    加班到搖擺

第三步,設置bug類的訪問權限緩存

  • 找到bug類,打開「權限設置」,爲裏面不一樣的權限分配到不一樣角色上。
  • 把add_fields(新建字段),create(新建數據),指定用戶到role_write上,即只有該角色能夠作新增的操做
  • 爲delete(刪除數據),find(搜索),get(根據id查詢),指定用戶到role_read上,即只有該角色能夠作查詢刪除操做
  • update(修改數據項)置空

    加班到搖擺

第四步,在vue組件中,添加登陸語句。安全

  • 在front-tool組件中使用role_write角色登陸。
  • 在Bug的管理系統(bug-list,bug-detail)中使用role_read角色登陸。
  • 方法很簡單,在初始化後面加一條登陸語句便可

    AV.init({
      appId: 'xxx',
      appKey: 'xxx',
    })
    AV.User.logIn('role_write', 'xxx')

front-tool功能擴展

接口檢測功能

  • 組件可配置一個接口檢測函數ajaxHook,利用該函數能夠對全部接口返回的數據進行檢查。
  • ajaxHook接受一個ajaxData參數,裏面記錄了當前接口的信息,如request,response,header等。可用於檢查接口狀態,對接口進行斷言。
  • 當接口返回的狀態碼不正確時,在函數中返回true,便可觸發數據上報功能。
  • 若是但願在生產環境也使用接口檢測功能,自動上報數據,須要將front-tool組件的useInProd參數設置爲true。
  • 該函數的意義在於檢測生產環境的接口報錯,作錯誤監控。
  • 開發同窗能夠天天上班前到Bug信息收集系統中經過URL檢索生產環境的bug信息,檢測線上問題。

    加班到搖擺

添加自定義菜單

  • 在front-tool組件中,咱們能夠添加一些自定義的方法
  • 實現某些便捷的功能,方便測試同窗使用的方法,提升效率,如「清除token,從新登陸」,「清除緩存信息等」
  • 使用方法進行菜單配置便可,如

    加班到搖擺

front-tool源碼解析

運行環境(域名環境)檢測

  • 原理很簡單,檢測當前頁面URL是否匹配特定的域名前綴。

    // 獲取運行環境
    getRuntimeEnv() {
      let envObj = {
        dev: 'xxx',
        test: 'xxx',
        pre: 'xxx',
        prod: 'xxx',
      }
      for (let key in envObj) {
        if (location.href.indexOf(envObj[key]) !== -1) {
          return key
        }
      }
      return 'local'
    }

全局方法注入及註銷

  • 組件建立時,會先獲取當前域名環境,並檢測useInProd參數,若是是生產環境且useInProd不爲真。則將全局暴露的函數所有設置爲空函數。
  • 不然進入初始化函數,覆寫ajax功能,往ajax相關函數中加入咱們想要的代碼。
  • 註冊各個全局函數
  • 其中AV是leanCloud定義的全局變量,獲取AV._config.applicationId,用以判斷leanCloud是否被初始化過。
  • 作這個檢測,是由於在vue項目開發中,熱編譯會導致部分代碼會被從新加載。致使leanCloud屢次加載而報錯。故須要在這裏先檢測是否已經被加載過,再去進行初始化操做。

    init() {
       this.resetAjax() // 覆寫ajax
       this.$root.__proto__.$addCustomData = this.addCustomData.bind(this) // 註冊全局函數
       this.$root.__proto__.$clearCustomData = this.clearCustomData.bind(this)
       this.$root.__proto__.$addGlobalData = this.addGlobalData.bind(this)
       this.$root.__proto__.$clearGlobalData = this.clearGlobalData.bind(this)
       window.$collectData = this.reportDate.bind(this)
       if (!AV._config.applicationId) { // 檢測AV是否已經被初始化過
         AV.init({
           appId: 'I7QMGWueNtd27ILAiMQqUAzI-gzGzoHsz',
           appKey: 'WRoSIQ8hSGLq9xLbaWDe9f7y',
         })
         AV.User.logIn('XtRuRZtca-for-add', 'Xw8XFNAidgEbJefXnCuG')
       }
     }

ajax覆寫,實現接口監聽

  • 一樣的,咱們經過window._hadResetAjax變量標識ajax是否已經被複寫過,防止屢次運行,屢次覆寫。
  • ajax覆寫的原理,

    • 是先將ajax本來的函數經過變量保存,
    • 覆寫ajax的函數,
    • 在覆寫的函數中加入咱們的代碼,並調用前面保存的ajax原始函數,
    • 至關於在ajax函數中添加hook。和vue的生命週期概念相似,運行到某環節時,執行某操做。
  • 須要注意的是,ajax的相關函數是定義在其原型鏈上的,在保存其原始函數時,須要保存原型鏈上的函數
  • 首先經過變量保存原始的window.XMLHttpRequest,及其原型鏈上的open,send,setRequestHeader方法

    let originXHR = window.XMLHttpRequest
    let originOpen = originXHR.prototype.open
    let originSend = originXHR.prototype.send
    let originSetRequestHeader = originXHR.prototype.setRequestHeader
  • 覆寫 window.XMLHttpRequest 構造函數

    • 在新的構造函數內部生成原始window.XMLHttpRequest對象的實例
    • 複寫open,send,setRequestHeader方法。複寫方法是讓其從新賦值爲一個新的函數,並在該函數中添加咱們的代碼。在最後調用一開始保存的,原始的window.XMLHttpRequest中對應的方法
    • 調用原始方法時,須要經過call方法修改this的指向,讓其指到window.XMLHttpRequest實例。
    • 在覆寫函數時,須要將接收的函數參數原封不動地傳會給原始的方法中,不能影響和改變函數的使用方式。這也是爲何不影響axios的使用,由於咱們覆寫的是ajax的底層實現。axios是在其內容上進行二次開發的,因此加載本組件不會對axios有任何影響
    • 原則上,能夠經過這種方式對ajax進行任意次的覆寫
  • 這裏介紹一下每一個覆寫函數的中作了什麼操做

    • 覆寫open,用於收集請求方式,請求連接,請求的query參數。
    • 覆寫setRequestHeader,用於收集請求的頭信息,以便收集如token等驗證信息。
    • 覆寫send,用於手機post請求的參數,timeout,responseType等信息。
  • 最後是監聽接口loadend事件,即接口響應事件

    • 記錄接口返回的內容。
    • 進行接口hook操做,調用父組件傳入的ajaxHook函數,並在函數中傳遞本次接口調用的相關信息。
    • 當ajaxHook返回true時,調用數據上報函數,上報數據。
    • 這裏作了一個優化,對請求的連接進行檢測,當接口是leanCloud的接口時,不進行數據記錄以及ajaxHook。
  • 若是你們對JS的繼承有印象的話,會發現,這個作法與JS的實例繼承相似,在內部生產父類的實例,對實例進行一系列操做後,返回這個實例

    // 重寫AJAX
    resetAjax() {
      if (window._hadResetAjax) { // 若是已經重置過,則再也不進入。解決開發時局部刷新致使從新加載問題
        return
      }
      window._hadResetAjax = true
      let originXHR = window.XMLHttpRequest
      let originOpen = originXHR.prototype.open
      let originSend = originXHR.prototype.send
      let originSetRequestHeader = originXHR.prototype.setRequestHeader
    
      // 重置事件
      window.XMLHttpRequest = () => {
        let ajaxData = {} // 整個ajax數據,收集數據時用
        let realXHR = new originXHR()
    
        // 重置操做函數,獲取請求數據
        realXHR.open = (method, url, asyn) => {
          ajaxData.request = {
            method: method,
            url: url.split('?')[0],
            data: this.getParams(url),
            header: {},
          }
          originOpen.call(realXHR, method, url, asyn)
        }
    
        // 重置設置請求頭的函數
        realXHR.setRequestHeader = (header, value) => {
          ajaxData.request.header[header] = value
          originSetRequestHeader.call(realXHR, header, value)
        }
    
        // 重置操做函數,獲取請求數據
        realXHR.send = (postData) => {
          ajaxData.request.timeout = realXHR.timeout
          ajaxData.request.responseType = realXHR.responseType
          if (postData) {
            ajaxData.request.data = typeof postData === 'string' ? this.getParams(`?${postData}`) : postData
          }
          try { // 防止timeout等報錯,形成程序阻塞
            originSend.call(realXHR, postData)
          } catch (e) {
            console.log(e)
          }
        }
    
         // 監聽加載完成,獲取回覆的報文
         realXHR.addEventListener('loadend', () => {
           ajaxData.response = realXHR.response
           if (ajaxData.request.url.indexOf('api.leancloud.cn') === -1) {
             this.ajaxList.push(ajaxData)
             if (this.ajaxHook) { // 外部執行鉤子
               this.ajaxHook(ajaxData) && this.reportDate()
             }
           }
         }, false)
         return realXHR
      }
    },

講完了,我要回去搬磚了,88

搬磚

相關文章
相關標籤/搜索