HTTP API認證授權方案


一.需求背景

在一些商業合作的場景下,合作方有自己的軟件系統並且具備開發能力,需要訪問我們的數據資源(比如:賬號、產品、統計等),一般的技術方案是提供HTTP API給合作方調用。此時為了保證數據的安全性以及對數據訪問范圍的控制,就必須驗證API調用方的身份,然后結合調用方的權限返回對應的資源,對於無法識別身份的調用方,服務端會進行攔截。

二.常用的API認證技術

2.1 App Secret Key + HMAC

這是一種用於給消息簽名的技術,我們怕消息在傳遞的過程中被人修改,所以,我們需要用對消息進行一個MAC算法,得到一個摘要字串,然后,接收方得到消息后,進行同樣的計算,然后比較這個MAC字符串,如果一致,則表明沒有被修改過(整個過程參看下圖)。而HMAC – Hash-based Authenticsation Code,指的是利用Hash技術完成這一工作,比如:SHA-256算法。

以SHA-256算法示例,簽名流程:

  1. 發送方以 Key 作為算法的簽名,對消息 Message 進行一個MAC算法,得到一個摘要字串 MAC
  2. 接收方 接收消息 Message 后進行同樣的計算得到一個摘要字串 MAC
  3. 接收方 然后比較這個 MAC 字符串是否一致,如果一致,則表明沒有被修改過。

2.2 OAuth 2.0

OAuth 是一個關於授權(authorization)的開放網絡標准,在全世界得到廣泛應用,目前的版本是2.0版。OAuth 2.0依賴於TLS/SSL的鏈路加密技術(HTTPS),完全放棄了簽名的方式,認證服務器再也不返回什么token secret的密鑰了。

2.2.1 Authorization Code Flow

Authorization Code 是最常使用的OAuth 2.0的授權許可類型,它適用於用戶給第三方應用授權訪問自己信息的場景。其流程圖如下:

授權流程:

  1. 當用戶 Resource Owner 訪問第三方應用 Client 的時候,第三方應用會把用戶帶到認證服務器 Authorization Server 上去。
  2. Authorization Server 收到這個URL請求后,其會通過 client_id 來檢查 redirect_uri 和 scope 是否合法,如果合法,則彈出一個頁面,讓用戶授權。(如果用戶沒有登錄,則先讓用戶登錄,登錄完成后,出現授權訪問頁面)
  3. 當用戶授權同意訪問以后,Authorization Server 會跳轉回 Client ,並以返回一個 Authorization Code。
  4. 接下來,Client 就可以使用 Authorization Code 獲得 Access Token。
  5. 最后就是用 Access Token 請求 Resource Server 用戶的資源。

2.2.2 Client Credential Flow

客戶端以自己的名義,而不是以用戶的名義,向"服務提供商"進行認證。在這種模式中,用戶直接向客戶端注冊,客戶端以自己的名義要求"服務提供商"提供服務,其實不存在授權問題。

授權流程:

  1. Client 用自己的 client_idclient_secretAuthorization Server 請求 Access Token。
  2. 然后 Client 使用Access Token訪問 Resource Server 相關的資源。

三.業內產品調研

3.1 微信支付

  1. 微信支付采用 App Secret Key + HMAC 簽名,首先介紹一下微信支付的大致原理:

    • 微信是支付系統的開發方,掌管整個支付系統,負責記賬。

    • 商家想要接入微信支付收銀,需要向微信支付部門申請商戶號。

    • 普通用戶通過微信點擊商家的付款鏈接,進行付款。

    • 微信后台記錄一筆用戶和商家之間的交易流水,然后通知商家系統支付成功。

    好了,現在可以知道,交易過程其實就是商家系統和微信后台的接口互相調用,而且只需要單向的關注商家調用微信后台。

  2. JSAPI支付-開發文檔,簽名算法:

    假設傳遞的參數如下:

    appid: wxd930ea5d5a258f4f
    mch_id: 10000100
    device_info: 1000
    body: test
    nonce_str: ibuaiVcKdpRxkhJA
    

    第一步,設所有發送或者接收到的數據為集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
    

    第二步,在stringA最后拼接上key得到stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將得到的字符串所有字符轉換為大寫,得到sign值signValue。

    stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d" //注:key為商戶平台設置的密鑰key
    sign=MD5(stringSignTemp).toUpperCase()="9A0A8659F005D6984697E2CA0A9CF3B7" //注:MD5簽名方式
    sign=hash_hmac("sha256",stringSignTemp,key).toUpperCase()="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6" //注:HMAC-SHA256簽名方式,部分語言的hmac方法生成結果二進制結果,需要調對應函數轉化為十六進制字符串。
    

    最終發送的數據:

    <xml>
      <appid>wxd930ea5d5a258f4f</appid>
      <mch_id>10000100</mch_id>
      <device_info>1000</device_info>
      <body>test</body>
      <nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
      <sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
    </xml>
    

3.2 微信公眾號

  1. 微信公眾號-獲取AccessToken 開發文檔

    access_token是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字符空間。access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效。

  2. 接口調用請求說明

    GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
    

    參數說明

    • grant_type:獲取access_token填寫client_credential
    • appid:第三方用戶唯一憑證
    • secret:第三方用戶唯一憑證密鑰,即appsecret

    返回情況

    正常情況下,微信會返回下述JSON數據包給公眾號:

    {"access_token":"ACCESS_TOKEN","expires_in":7200}
    

    參數說明

    • access_token:獲取到的憑證
    • expires_in:憑證有效時間,單位:秒

3.3 微信網頁授權

  1. 微信網頁授權-開放文檔

    如果用戶在微信客戶端中訪問第三方網頁,公眾號可以通過微信網頁授權機制,來獲取用戶基本信息,進而實現業務邏輯。

  2. 網頁授權AccessToken的流程

    • 第一步:引導用戶進入授權頁面同意授權,獲取code
    • 第二步:通過code換取網頁授權access_token
    • 第三步:如果需要,開發者可以刷新網頁授權access_token,避免過期。
    • 第四步:通過網頁授權access_token和openid獲取用戶基本信息

四、如何選擇HTTP API鑒權方案

4.1 HTTP API鑒權方式的對比

前面介紹幾種常用的API鑒權技術,在產品調研環節分別可以找到其落地場景。

首先拿 微信支付 來看,一筆交易的下單在一個接口中完成,請求的參數包含金額、商戶號等,都是非常關鍵的參數,必須要求嚴格校驗,防止被攻擊篡改,同時參數還有時效限制(含時間戳)。此時自然不適合使用OAuth 2.0的鑒權方式,AccessToken的請求不對參數進行校驗。

然后看下 微信公眾號 AccessToken的場景,可以看到使用AccessToken調用接口(管理公眾號菜單、管理賬號)都屬於一個企業范圍內的數據,可以這么理解,這部分信息屬於微信授權給企業的一份獨立資產,公眾號對應的企業有權限管理這份資產。此時使用AccessToken可以很好的控制訪問范圍。這里不是不能用 App Secret Key + HMAC 的鑒權方式,而是覺得這部分信息安全要求沒有支付高。另一方面,不對參數加密,通信也會更加高效(加密有耗時,比如文件上傳也不太適合進行加密)。

最后看下 微信網頁授權,同理類推,用戶的信息屬於每個獨立的用戶,獲取的AccessToken的訪問范圍也只能是當前用戶的信息。

4.2 HTTP API鑒權經驗分享

上面提到的兩種鑒權方式,無論是作為服務方還是調用方,我都在工作中都有使用到。個人覺得 App Secret Key + HMAC 實踐起來相對容易,客戶端對服務端的調用比較直接,鑒權不通過時可以通過接口的響應及時獲得反饋。

另一種,OAuth 2.0的AccessToken的方式,服務端需要維護AccessToken,並且還要控制AccessToken的失效,拿微信公眾號來看,新的AccessToken生成后,舊的AccessToken在5分鍾之內有效;客戶端需要維護一份AccessToken並及時刷新保持有效。再看下業務的交互上,比起 App Secret Key + HMAC 明顯多一些環節,環節多了就容易犯錯。

4.3 結論

最后,具體選擇使用哪一種鑒權方式,我想還是需要結合對應的業務場景來看。比如業務發展的初期,需要快速開發推向市場,這時就沒必要糾結,直接選擇一種相對而言簡單且不容易犯錯的 App Secret Key + HMAC 簽名鑒權。等到后續用戶量大了,業務成熟了,可以參考 微信公眾號、AWS s4簽名,精細划分每一個AccessToken的訪問范圍。

五.實踐-方案實現

實踐案例使用 App Secret Key + HMAC 的鑒權方式,下面會詳細介紹 客戶端簽名服務端驗簽 的過程。

5.1 分配AppId和AppSecret

在簽名之前首先需要分配 AppId 和 AppSecret,落實到業務場景中,這個就是我們作為資源方分配給合作方的租戶配置。關於 AppId 和 AppSecret 的生成沒有標准規范,每家的生成算法都不一樣,也都不會公布出來。本次案例,我們使用32位的uuid作為AppId,以64位的hash串作為AppSecret:

// 生成AppId
private static String generateAppId() {
    UUID uuid = UUID.randomUUID();
    return uuid.toString().replaceAll("-", "");
}
// 生成AppSecret
private static String generateAppSecret() {
    UUID uuid = UUID.randomUUID();
    return DigestUtils.sha256Hex(uuid.toString());
}

計算得出:

APPID = "ivv49q404zfp8075ivbcwye4ardqafha"

APP_SECRET = "ut338c829x2yzfnklvy8lezyu3ndsss68dyzo9opt3icbin7lv7p2j4b0i2cvjz8"

5.2 客戶端簽名

  1. 假設傳遞的參數如下:

    private static final String APPID = "ivv49q404zfp8075ivbcwye4ardqafha";
    
    /**
     * 下單請求對象
     */
    class PlaceOrderForm {
        String appid;
        Integer totalAmount;
        String body;
        String detail;
        String nonceStr;
    }
    
    /**
     * 模擬請求對象
     */
    private static PlaceOrderForm mockWebForm () {
        PlaceOrderForm form = new PlaceOrderForm();
        form.appid = APPID;
        form.body = "test";
        form.detail = "test";
        form.nonceStr = "123456";
        form.totalAmount = 88;
        return form;
    }
    
  2. 第一步,設所有發送或者接收到的數據為集合M,將集合M內非空參數值的參數按照參數名ASCII碼從小到大排序(字典序),使用URL鍵值對的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

    /**
     * TreeMap會根據Key排序
     */
    private static Map<String, String> confirmToMap(PlaceOrderForm form) throws Exception {
        Map<String, String> map = new TreeMap<>();
        Field[] fields = PlaceOrderForm.class.getDeclaredFields();
        for (Field field : fields) {
          field.setAccessible(true);
          Object value = field.get(form);
          if (value != null && !field.getName().equals("sign")) {
            if (value instanceof String) {
              map.put(field.getName(), (String) value);
            } else if (value instanceof Integer) {
              map.put(field.getName(), String.valueOf(value));
            }
          }
        }
        return map;
    }
    
  3. 第二步,在stringA最后拼接上appsecret得到stringSignTemp字符串,並對stringSignTemp進行MD5運算,再將得到的字符串所有字符轉換為大寫,得到sign值signValue。

    private static final String APP_SECRET = "ut338c829x2yzfnklvy8lezyu3ndsss68dyzo9opt3icbin7lv7p2j4b0i2cvjz8";
    
    /**
     * 生成簽名
     */
    private static String sign(Map<String, String> params) {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> entry : params.entrySet()) {
          sb.append(entry.getKey());
          sb.append("=");
          sb.append(entry.getValue());
          sb.append("&");
        }
        sb.append("appsecret=");
        sb.append(APP_SECRET);
        return DigestUtils.md5Hex(sb.toString()).toUpperCase();
    }
    
  4. 最后計算得到摘要

    public static void main(String[] args) {
      PlaceOrderForm form = mockWebForm();
      try {
        Map<String, String> stringStringMap = confirmToMap(form);
        String sign = sign(stringStringMap);
        System.out.println(sign);
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    

5.3 服務端驗簽

服務端接收請求的參數,使用同樣的簽名算法計算出摘要 sign 進行比較,如果一致,則說明請求沒有被修改。

六.參考資料

  1. HTTP API 認證授權術 || 酷殼 - CoolShell

  2. Signature Version 4 規范請求 - AWS General Reference

  3. go語言並發編程與Context


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM