偽前端花式調介面

Denzel發表於2021-11-29

背景

作為你一個前端,可能你常常自詡,你是一個無情的API呼叫機器,調框架API,調服務端API;但下文可能會震驚到你,至少已經讓我崩潰了好幾天。

從10月就接到一項需求,需要將我們平臺的資料同步到集團另一個平臺,how? 通過開放API!!!

這個開放API 有多難調,我大概描述一下

  • 對稱加密,對方平臺將給我發放一對祕鑰;這似乎所有API都會這樣做,畢竟安全第一!!!
  • MD5計算, 哦不,是MD5計算後轉base64;這個其實也正常,為了資料傳輸防篡改,請求bodymd5加密也很常見;
  • 請求頭摘要加簽,加簽是什麼鬼?摘要是什麼鬼?沒錯,還是為了安全,而且這個玩法很複雜,是我以前沒見過的;
  • 只有線上環境可調,線下不可調,what??;但最難的是,環境隔離,就是不能跨機房呼叫,大白話就是線下機房不能調線上機房(玩法類似於我高中時的小靈通,就是隻能撥本市的號碼,異地戀是不可能的),這預示著什麼?

預示著熱更新不可用,本地調不可行?改一行程式碼,就需要去預發環境(預發環境屬於線上機房)部署一次(> 5min), 然後調一下剛剛部署的介面,發現不對,然後又改一行程式碼,又部署,又調,不斷重複!!!Seriously???
image.png
可能你現在想知道,是哪個平臺商提供了這個API, 悄悄告訴你, 算了,你還是自己看吧:https://help.aliyun.com/docum...

花式調介面

postman 是一個好工具,但有時postman也可能力不從心(postwoman也一樣),比如今天要講的機房內呼叫。
所以,下面講到的招式,可能常規前端開發不一定遇得到,也不一定用得上,但看一看又不要錢,萬一賺了喃。

招式1:動態傳參

通常我們調服務端API介面不通,通常只有兩種情況:

  • 服務端傻逼,寫了一個根本調不通的介面(我這裡不存在,因為傳說是通的,至少他向我證明了閘道器是通的);
    image.png
  • 前端傻逼,引數沒按照指示來

所以,我預設按我是傻逼的方案來做,就是服務端的入參,我都通過我的介面動態傳上去,哪不對我就改哪裡,聽起來可能有點懵逼,我畫個圖:有點草率。
image.png
但事情,原沒有我想的那麼簡單,我確實懵逼了,因為這個介面,遠不是入參正確就能調通那麼簡單。
因為我還得md5計算正確、請求頭設定正確、簽名正確;

而這些請求頭也需要動態改變,不過這時我靈光乍現:上面這個防止自己傻逼方案, 和介面代理有什麼區別, 所以我頓時有了方案2:介面代理

方案2:介面代理

proxy, 前端可能都不陌生,webpack-dev-server 就有這個功能,多是用來解決介面跨域。所以,我何必要什麼動態傳參,我直接加個代理不更簡單,所以方案是這樣的:
image.png
兩行程式碼搞定:

// plugin.js加入外掛
export const httpProxy = {
  enable: true,
  package: 'egg-http-proxy',
};

// config.js 加入配置
  config.httpProxy = {
    '/aapi': {
      target: 'http://api-gateway.test.com',
      pathRewrite: {'^/aoneapi' : ''}
    }
  };

這個方案,聽起來很美好,熱更新有了,線下除錯有了;但由於我們平臺底層有一些中介軟體,所以當目標平臺響應了400 或 401,我們平臺(node-server) 就會攔截,返回一個302重定向, 這就會導致我看不到目標平臺真實的錯誤響應,但改底層攔截改動很大,也會影響到其他同事的開發。

方案3: 遠端server直接調

鑑於前面的curl嘗試, 證明最直接的介面呼叫,最短的鏈路,可以避免最少的錯誤

curl -v -X GET http://test.goaway.com/checkpreload -H 'Authorization: APPCODE f7f526fd3adf2f38d46'

因為NodeJs 是指令碼語言,區別於Java這種編譯型語言。這時候好處就體現了,考慮到伺服器上有原始碼,那就可以直接通過 node 命令去喚起介面呼叫, 所以現在鏈路變成了這樣:
image.png
我在我的倉庫中加入了一個檔案(test.js),虛擬碼:

const rp = require('request-promise');
const { createHash, createHmac } = require('crypto');

async function handle(str = []) {
  // 省去具體實現
}

// 獲取請求相關資料
const arg = process.argv.slice(ArgStart);
handle(arg);

所以當我把程式碼部署上去時,我就可以執行node test.js ..., like:
image.png
這樣執行一下,我能自定義響應體,可以看到請求頭是否符合規範,錯誤發生時可以清楚看到錯誤響應;最重要的,當我知道錯誤時,我可以直接在伺服器上編輯程式碼,然後再接著執行命令測試,這樣時間就大大節省了。這一切的實現,都歸功於NodeJs一門指令碼語言!!!
image.png

分享個知識: md5

這一次除錯,我的卡點可以大概糾結於四個階段:

  • 機房隔離,網路不通
  • Invalid md5
  • Invalid signalture
  • Authentication error since of operator not configured according to ak

卡的時間最久的,就是Invalid md5;由於最開始官方了提供了SDK,裡面提供了md5計算方法:

static getContentMD5(body) {
  const hash = crypto_1.createHash('md5');
  hash.update(body);
  return hash.digest('hex');
}

但問題,就出在這,這個MD5計算和官方文件提到的不一致:
image.png
可以看出,在經過md5計算後,又用base64的方式進行了編碼。
而官方SDK提供的只需要如下修改一下,就可以獲得一致的結果:

static getContentMD5(body) {
  const hash = crypto_1.createHash('md5');
  hash.update(body);
  return hash.digest('base64');
}

那digest方法這個入參到底有什麼機密?

細品digest入參

NodeJs官方文件

在crypto:0.1.92版本,入參的typescript是這樣定義的;

type BinaryToTextEncoding = "base64" | "base64url" | "hex"

除了上面這三個引數(在0.1.94版本,已經沒有base64url選項),其實這個入參還能為空, 為空時返回Buffer, 否則返回string, 所有在日常場景入參都是存在的,因為我們拿著一個Buffer 意義不大,當對hello world做md5計算時,得到的buffer是: <Buffer fc 3f f9 8e 8c 6a 0d 30 87 d5 15 c0 47 3f 86 77>
那為什麼不直接用Buffer,因為物件不利於傳輸!!!

base64 vs hex

又可以理解為base64 vs base16;
所以當我們傳入hex時,達到的結果是:fc3ff98e8c6a0d3087d515c0473f8677,這個結果正好就是上面的buffer字串化。

而傳入base64, 得到的結果是:/D/5joxqDTCH1RXARz+Gdw==
因為Buffer 本身是由一串串16進位制陣列成的,所以轉為hex,就會很容易,而轉成base64,就需要再編碼

關於base64編碼:網路上傳輸的字元並不全是可列印的字元,比如二進位制檔案、圖片等。Base64的出現就是為了解決此問題,它是基於64個可列印的字元(A-Z、a-z、0-9、+、/)來表示二進位制的資料的一種方法;轉碼過程:首先將待轉換的字元每三個位元組分為一組,每個位元組佔8bit,那麼共有24個二進位制位,然後將二進位制位每6個一組分為4組。在每組前面新增兩個0,每組由6個變為8個二進位制位,總共32個二進位制位,即四個位元組。最後根據Base64編碼對照表獲得對應的值(位數不足情況:當位數從8bit轉化6bit時,不完整的6bit中用0填充,無對應6bit的8bit向後直接用=填充
這裡粗略做個變換,hex結果是32個字元,每個字元為4個二進位制數
第一步: 轉化為二進位制數:1111-1010-0011.....-0111-0111(32 * 4 = 128)

第二步:轉化為6個bit為一小組,然後三個一大組: 111110-100011-.....-011101-11(7 * 3 * 6 + 2)

第三步:補0,補=:00111110-00100011-....-00011101-00110000-=-=
 
第四步:對照表轉字元:/-D-....-d-w-=-=

至少頭尾是一致的,套路應該沒啥問題。
至於何時用base64, 何時用base16; 對於md5計算,這個主要看服務端心情!但對於大位元組流,比如檔案,圖片的傳輸,都會選用base64, 因為這可以節省流量,提高傳輸效率。

那base64Url 又是什麼? base64Url 又稱為安全的Base64,和 base64的區別僅僅在於63號和64號字元的轉換上;
image.png
由於"/","="等是URL中的保留字元或不安全字元,因此如果直接在URL中傳輸Base64編碼,保留字元和不安全字元會被替換為%XX的形式,對後端來說解碼不方便。如果不替換,就會造成URL注入漏洞。所以 base64Url 其目的在於解決+、\、=在url的傳輸問題。

結尾

寫個文章也要有個儀式感,不能虎頭蛇尾,這裡就結尾感嘆一聲吧:做個前端好難!!!

image.png

相關文章