博客 / 詳情

返回

花式調接口: hex vs base64

背景

作為你一個前端,可能你常常自詡,你是一個無情的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

歡迎關注我的前端公眾號:前端黑洞

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.