天天看點

小程式安全指南:如何禁止外部直接跳轉到小程式某頁面

背景

小程式也需要注意安全性。例如某些頁面是業務流程中的「第二步」、「第三步」,而非「第一步」。如果外部小程式、外部二維碼、連結直接跳轉到了我們小程式的「第二步」、「第三步」,可能有超出預期的事情發生。

針對外部跳轉到小程式「第二步」、「第三步」頁面的,我們應該攔截掉,要麼直接報錯:頁面來源非法,要麼直接跳回首頁。

關鍵問題在于,這種邏輯該怎麼實作最優雅呢?

一種不是很好的解決方案

如果你的業務流程采用了狀态機模型,并在後端存儲了狀态,那麼可以在每一個頁面onLoad時,發送一個API請求,判斷目前狀态和目前頁面是否比對,如果比對,則正常通路,如果不比對,則跳回到狀态對應的頁面。

這依賴于後端實作,不太合适。

更好的解決方案

我們考慮純前端的實作。

問題關鍵在于:我們要禁止外部直接跳轉到我們小程式的部分頁面。我們需要要區分:内部跳轉、外部跳轉。

這部分頁面,隻允許通過内部跳轉API(​

​wx.redirectTo​

​​、​

​wx.navigateTo​

​)來跳轉,其它方式都不應跳轉到。

隻要我們在調用​

​wx.redirectTo​

​​、​

​wx.navigateTo​

​​時,都加上一個​

​特殊參數​

​​。然後在頁面的onLoad裡面判斷,是否包含了該​

​特殊參數​

​​,包含該​

​特殊參數​

​​,表明是内部跳轉,不包含該​

​特殊參數​

​,表明是外部跳轉。

這個​

​特殊參數​

​​不可以被猜到,如果被猜到了,那麼外部跳轉時帶上​

​特殊參數​

​,該方案就失效了。

是以,這個​

​特殊參數​

​必須不是固定的,要是随機的。

我們可以參考WEB中針對CSRF的解決方案,如果使用随機的​

​特殊參數​

​​,讓外部無法猜到這個​

​特殊參數​

​,那麼問題就解決了。

​特殊參數​

​​什麼時候生成呢?可以在App onLaunch時生成,也可以在「第一步」頁面onLoad時生成。不過不論怎樣,這個​

​特殊參數​

​都需要作為全局變量儲存在記憶體中,友善随時引用和判斷。

具體怎麼做?建議你先閱讀下文章:​​《如何全局重寫小程式 Page函數 wx對象?》​​,學會這種方法,我們再來看下方的代碼。

全局改寫Page的onLoad生命周期,增加校驗

const WHITE_LIST = ['pages/index'];

function onLoadProxy(onLoad) {
  return function (query) {
    const app = getApp();
    // 以下是token攔截邏輯:
    if (WHITE_LIST.includes(this.route)) {
      // 在允許外部跳轉來的白名單頁面,生成随機數validEntranceToken
      app.validEntranceToken = `${new Date().getTime()}${Math.random().toString(36)}`;
    } else if (query.validEntranceToken !== app.validEntranceToken) {
      // 其它頁面,校驗參數token是否與全局變量中token一緻,若不一緻,跳轉到報錯頁面
      wx.redirectTo({ url: `/pages/fail` });
      return;
    }
    // 未被攔截,表明是正常來源。以下是正常流程:
    if (onLoad) {
      return onLoad.call(this, query);
    }
  };
}

const PageProxy = (Page) => function (options) {
  const newOptions = {
    ...options,
    onLoad: onLoadProxy(options.onLoad),
  };
  Page(newOptions);
};

Page = PageProxy(Page);      

全局改寫wx.navigateTo方法,附帶參數

function addValidEntranceToken(url) {
  const app = getApp();
  const symbol = url.includes('?') ? '&' : '?';
  return `${url}${symbol}validEntranceToken=${app.validEntranceToken}`;
}

export function redirectToProxy(redirectTo) {
  return function (object) {
    return redirectTo({
      ...object,
      url: addValidEntranceToken(object.url),
    });
  };
}

function wxProxy(wx) {
  const newWx = { ...wx };
  newWx.navigateTo = redirectToProxy(wx.navigateTo);
  newWx.redirectTo = redirectToProxy(wx.redirectTo);
  return newWx;
}

wx = wxProxy(wx);      

解釋

我們通過修改所有Page的onLoad方法,當使用者通路「白名單」頁面時,不會做任何攔截,而是直接生成一個随機的​

​validEntranceToken​

​​。當使用者通路「白名單」以外的頁面時,則會判斷參數中是否包含正确的​

​validEntranceToken​

​,若不包含,則會跳轉到報錯頁。若包含,則繼續執行該頁面的其它邏輯。

繼續閱讀