當前位置: 妍妍網 > 碼農

完整清晰,三方介面呼叫設計方案

2024-05-25碼農

架構師(JiaGouX)

我們都是架構師!
架構未來,你來不來?

在為第三方系統提供介面的時候,肯定要考慮介面數據的安全問題,比如數據是否被篡改,數據是否已經過時,數據是否可以重復送出等問題。

在設計三方介面呼叫的方案時,需要考慮到安全性和可用性。以下是一種設計方案的概述,其中包括使用API金鑰( Access Key/Secret Key )進行身份驗證和設定回呼地址。

設計方案概述

1.API金鑰生成: 為每個三方套用生成唯一的API金鑰對(AK/SK),其中AK用於標識套用,SK用於進行簽名和加密。

AK:Access Key Id,用於標示使用者。

SK:Secret Access Key,是使用者用於加密認證字串和用來驗證認證字串的金鑰,其中SK必須保密。

透過使用Access Key Id / Secret Access Key加密的方法來驗證某個請求的發送者身份。

2.介面鑒權: 在進行介面呼叫時,客戶端需要使用AK和請求參數生成簽名,並將其放入請求頭或參數中以進行身份驗證。

3.回呼地址設定: 三方套用提供回呼地址,用於接收異步通知和回呼結果。

4.介面API設計: 設計介面的URL、HTTP方法、請求參數、響應格式等細節。

許可權劃分

appID:套用的唯一標識

用來標識你的開發者帳號的,即:使用者id,可以在資料庫添加索引,方便快速尋找,同一個 appId 可以對應多個 appKey+appSecret ,達到許可權的

appKey:公匙(相當於帳號)

公開的,呼叫服務所需要的金鑰。是使用者的身份認證標識,用於呼叫平台可用服務.,可以簡單理解成是帳號。

appSecret:私匙(相當於密碼)

簽名的金鑰,是跟appKey配套使用的,可以簡單理解成是密碼。

token:令牌(過期失效)

使用方法

  • 向第三方伺服器請求授權時,帶上AppKey和AppSecret(需存在伺服器端)

  • 第三方伺服器驗證appKey和appSecret在資料庫、緩存中有沒有記錄

  • 如果有,生成一串唯一的字串(token令牌),返回給伺服器,伺服器再返回給客戶端

  • 後續客戶端每次請求都需要帶上token令牌

  • 為什麽 要有appKey + appSecret 這種成對出現的機制呢?

    因為要加密, 通常用在首次驗證(類似登入場景),用 appKey(標記要申請的許可權有哪些) + appSecret(密碼, 表示你真的擁有這個許可權)來申請一個token,就是我們經常用到的 accessToken(通常擁有失效時間),後續的每次請求都需要提供accessToken 表明驗證許可權透過。

    現在有了統一的appId,此時如果針對同一個業務要劃分不同的許可權,比如同一功能,某些場景需要唯讀許可權,某些場景需要讀寫許可權。這樣提供一個appId和對應的秘鑰appSecret就沒辦法滿足需求。 此時就需要根據許可權進行帳號分配,通常使用appKey和appSecret。

    由於 appKey 和 appSecret 是成對出現的帳號, 同一個 appId 可以對應多個 appKey+appSecret ,這樣平台就為不同的 appKey+appSecret 對分配不一樣的許可權。

    可以生成兩對appKey和appSecret。一個用於刪除,一個用於讀寫,達到許可權的細粒度劃分。如 : appKey1 + appSecect1 只有刪除許可權 但是 appKey2+appSecret2 有讀寫許可權… 這樣你就可以把對應的許可權 放給不同的開發者。其中許可權的配置都是直接跟appKey 做關聯的,appKey 也需要添加資料庫索引, 方便快速尋找

    簡化的場景:

    第一種場景: 通常用於開放性介面,像地圖api,會省去 app_id app_key ,此時相當於三者相等,合而為一 appId = appKey = appSecret 。這種模式下,帶上 app_id 的目的僅僅是統計某一個使用者呼叫介面的次數而已了。

    第二種場景: 當每一個使用者有且僅有一套許可權配置 可以去掉 appKey,直接將 app_id = app_key ,每個使用者分配一個 appId+ appSecret 就夠了。

    也可以采用簽名(signature)的方式: 當呼叫方向服務提供方法發起請求時,帶上(appKey、時間戳timeStamp、隨機數nonce、簽名sign) 簽名sign 可以使用 (AppSecret + 時間戳 + 隨機數)使用sha1、md5生成,服務提供方收到後,生成本地簽名和收到的簽名比對,如果一致,校驗成功

    簽名流程

    簽名規則

    1.分配appId(開發者標識)和appSecret(金鑰),給 不同的呼叫方

    可以直接透過平台線上申請,也可以線下直接頒發。appId是全域唯一的,每個appId將對應一個客戶,金鑰appSecret需要高度保密。

    2.加入timeStamp(時間戳),以伺服端當前時間為準,單位為ms ,5分鐘內數據有效

    時間戳的目的就是為了減輕DOS攻擊。防止請求被攔截後一直嘗試請求介面。伺服器端設定時間戳閥值,如果伺服器時間 減 請求時間戳超過閥值,表示簽名超時,介面呼叫失敗。

    3.加入臨時流水號nonce,至少為10位 ,有效期內防重復送出。

    隨機值nonce 主要是為了增加簽名sign的多變性,也可以保護介面的冪等性,相鄰的兩次請求nonce不允許重復,如果重復則認為是重復送出,介面呼叫失敗。

  • 針對查詢介面,流水號只用於日誌落地,便於後期日誌核查。

  • 針對辦理類介面需校驗流水號在有效期內的唯一性,以避免重復請求。

  • 透過在介面簽名請求參數加上 時間戳timeStamp + 隨機數nonce 可以防止 」重放攻擊「

    1. 時間戳(timeStamp):

    以伺服端當前時間為準,伺服端要求客戶端發過來的時間戳,必須是最近60秒內(假設值,自己定義)的。

    這樣,即使這個請求即使被截取了,也只能在60s內進行重放攻擊。

    1. 隨機數(nonce):

    但是,即使設定了時間戳,攻擊者還有60s的攻擊時間呢!

    所以我們需要在客戶端請求中再加上一個隨機數(中間黑客不可能自己修改隨機數,因為有參數簽名的校驗呢),伺服端會對一分鐘內請求的隨機數進行檢查,如果有兩個相同的,基本可以判定為重放攻擊。

    因為正常情況下,在短時間內(比如60s)連續生成兩個相同nonce的情況幾乎為0

    伺服端「第一次」在接收到這個nonce的時候做下面行為:

    1. 去redis中尋找是否有key為nonce:{ nonce}的數據

    2. 如果沒有,則建立這個key,把這個key失效的時間和驗證timestamp失效的時間一致,比如是60s。

    3. 如果有,說明這個key在60s內已經被使用了,那麽這個請求就可以判斷為重放請求。

    4.加入簽名欄位sign,獲取呼叫方傳遞的簽名資訊。

    透過在介面簽名請求參數加上 時間戳appId + sign 解決身份驗證和防止 」參數篡改「

    1. 請求攜帶參數appId和Sign,只有擁有合法的身份appId和正確的簽名Sign才能放行。這樣就解決了身份驗證和參數篡改問題。

    2. 即使請求參數被劫持,由於獲取不到appSecret(僅作本地加密使用,不參與網路傳輸),也無法偽造合法的請求。

    以上欄位放在請求頭中。

    API介面設計

    根據你的具體需求和業務場景,以下是一個簡單範例的API介面設計:

    1. 獲取資源列表介面
  • URL: /api/resources

  • HTTP 方法: GET

  • 請求參數:

  • page (可選): 頁碼

  • limit (可選): 每頁限制數量

  • 響應:

  • 成功狀態碼: 200 OK

  • 響應體: 返回資源列表的JSON陣列

  • 2. 建立資源介面
  • URL: /api/resources

  • HTTP 方法: POST

  • 請求參數:

  • name (必填): 資源名稱

  • description (可選): 資源描述

  • 響應:

  • 成功狀態碼: 201 Created

  • 響應體: 返回新建立資源的ID等資訊

  • 3. 更新資源介面
  • URL: /api/resources/{resourceId}

  • HTTP 方法: PUT

  • 請求參數:

  • resourceId (路徑參數, 必填): 資源ID

  • name (可選): 更新後的資源名稱

  • description (可選): 更新後的資源描述

  • 響應:

  • 成功狀態碼: 200 OK

  • 4. 刪除資源介面
  • URL: /api/resources/{resourceId}

  • HTTP 方法: DELETE

  • 請求參數:

  • resourceId (路徑參數, 必填): 資源ID

  • 響應:

  • 成功狀態碼: 204 No Content

  • 安全性考慮

    為了確保安全性,可以采取以下措施:

  • 使用HTTPS協定進行數據傳輸,以保護通訊過程中的數據安全。

  • 在請求中使用AK和簽名進行身份驗證,並對請求進行驗簽,在伺服端進行校驗和鑒權,以防止非法請求和重放攻擊。

  • 對敏感數據進行加密傳輸,例如使用TLS加密演算法對敏感數據進行加密。

  • 以上是一個簡單的設計方案和API介面設計範例。具體的實作細節可能因計畫需求而有所不同。在實際開發中,還要考慮錯誤處理、異常情況處理、日誌記錄等方面。

    防止重放攻擊和對敏感數據進行加密傳輸都是保護三方介面安全的重要措施。以下是一些範例程式碼,展示了如何實作這些功能。

    防止重放攻擊

    抓取報文原封不動重復發送如果是付款介面,或者購買介面就會造成損失,因此需要采用防重放的機制來做請求驗證,如請求參數上加上timestamp時間戳+nonce隨機數。

    重放攻擊是指黑客透過抓包的方式,得到客戶端的請求數據及請求連線,重復的向伺服器發送請求的行為。

    時間戳(tamp) + 數位簽名(sign), 也就是說每次發送請求時多傳兩個參數,分別為 tamp 和 sign。

    數位簽名的作用是為了確保請求的有效性。因為簽名是經過加密的,只有客戶端和伺服器知道加密方式及金鑰(key),所以第三方模擬不了。我們透過對sign的驗證來判斷請求的有效性,如果sign驗證失敗則判定為無效的請求,反之有效。 但是數位簽名並不能阻止重放攻擊,因為黑客可以抓取你的tamp和sign(不需做任何修改),然後發送請求。這個時候就要對時間戳進行驗證。

    時間戳的作用是為了確保請求的時效性。我們將上一次請求的時間戳進行儲存,在下一次請求時,將兩次時間戳進行比對。如果此次請求的時間戳和上次的相同或小於上一次的時間戳,則判定此請求為過時請求,無效。因為正常情況下,第二次請求的時間肯定是比上一次的時間大的,不可能相等或小於。

    如果修改了時間戳來滿足時間的時效性,sign驗簽就不透過了。

    註:如果客戶端是js,一定要對js做程式碼混淆,禁止右鍵等。

    1. 使用Nonce和Timestamp

    在請求中添加唯一的Nonce(隨機數)和Timestamp(時間戳),並將其包含在簽名計算中。伺服端在驗證簽名時,可以檢查Nonce和Timestamp的有效性,並確保請求沒有被重放。

    防止重放攻擊是在三方介面中非常重要的安全措施之一。使用Nonce(一次性隨機數)和Timestamp(時間戳)結合起來,可以有效地防止重放攻擊。下面是實作此功能的最佳實踐:

    生成Nonce和Timestamp:

  • Nonce應該是一個隨機的、唯一的字串,可以使用UUID或其他隨機字串生成演算法來建立。

  • Timestamp表示請求的時間戳,通常使用標準的Unix時間戳格式(以秒為單位)。

  • 在每個請求中包含Nonce和Timestamp:

  • 將生成的Nonce和Timestamp作為參數添加到每個請求中,可以透過URL參數、請求頭或請求體的方式進行傳遞。

  • 確保Nonce和Timestamp在每個請求中都是唯一且正確的。

  • 伺服器端驗證Nonce和Timestamp:

  • 在伺服器端接收到請求後,首先驗證Nonce和Timestamp的有效性。

  • 檢查Nonce是否已經被使用過,如果已經被使用過,則可能是重放攻擊,拒絕該請求。

  • 檢查Timestamp是否在合理的時間範圍內,如果超出預定的有效期,則認為請求無效。

  • 儲存和管理Nonce:

  • 為了驗證Nonce是否已經被使用過,伺服器需要儲存已經使用過的Nonce。

  • 可以使用資料庫、緩存或其他持久化儲存方式來管理Nonce的狀態。

  • 需要定期清理過期的Nonce,以防止儲存占用過多的資源。

  • 設定有效期:

  • 為了限制請求的有效時間範圍,可以設定一個合理的有效期。

  • 根據實際需求和業務場景,選擇適當的有效期,例如幾分鐘或幾小時。

  • 透過使用Nonce和Timestamp來防止重放攻擊,可以保護三方介面免受惡意重放請求的影響。以上是實作該功能的最佳實踐,但具體的實作方法可能因應用程式和技術棧的不同而有所差異。確保在設計和實施安全措施時考慮到應用程式的特定需求和風險模型。

    2. 添加過期時間

    在請求中添加一個過期時間欄位(例如,token的有效期),並在伺服端驗證請求的時間戳是否在有效期內。超過過期時間的請求應被拒絕。

    防篡改、防重放攻擊攔截器

    每次HTTP請求,都需要加上timestamp參數,然後把timestamp和其他參數一起進行數位簽名。HTTP請求從發出到達伺服器一般都不會超過60s,所以伺服器收到HTTP請求之後,首先判斷時間戳參數與當前時間相比較,是否超過了60s,如果超過了則認為是非法的請求。

    一般情況下,從抓包重放請求耗時遠遠超過了60s,所以此時請求中的timestamp參數已經失效了,如果修改timestamp參數為當前的時間戳,則signature參數對應的數位簽名就會失效,因為不知道簽名秘鑰,沒有辦法生成新的數位簽名。

    但這種方式的漏洞也是顯而易見的,如果在60s之後進行重放攻擊,那就沒辦法了,所以這種方式不能保證請求僅一次有效 nonce的作用

    nonce的意思是僅一次有效的隨機字串,要求每次請求時,該參數要保證不同。我們將每次請求的nonce參數儲存到一個「集合」中,每次處理HTTP請求時,首先判斷該請求的nonce參數是否在該「集合」中,如果存在則認為是非法請求。

    nonce參數在首次請求時,已經被儲存到了伺服器上的「集合」中,再次發送請求會被辨識並拒絕。

    nonce參數作為數位簽名的一部份,是無法篡改的,因為不知道簽名秘鑰,沒有辦法生成新的數位簽名。

    這種方式也有很大的問題,那就是儲存nonce參數的「集合」會越來越大。

    nonce的一次性可以解決timestamp參數60s(防止重放攻擊)的問題,timestamp可以解決nonce參數「集合」越來越大的問題。

    public classSignAuthInterceptorimplementsHandlerInterceptor{
    private RedisTemplate<String, String> redisTemplate;
    private String key;
    publicSignAuthInterceptor(RedisTemplate<String, String> redisTemplate, String key){
    this.redisTemplate = redisTemplate;
    this.key = key;
    }
    @Override
    publicbooleanpreHandle(HttpServletRequest request,
    HttpServletResponse response, Object handler)
    throws Exception 
    {
    // 獲取時間戳
    String timestamp = request.getHeader("timestamp");
    // 獲取隨機字串
    String nonceStr = request.getHeader("nonceStr");
    // 獲取簽名
    String signature = request.getHeader("signature");
    // 判斷時間是否大於xx秒(防止重放攻擊)
    long NONCE_STR_TIMEOUT_SECONDS = 60L;
    if (StrUtil.isEmpty(timestamp) || DateUtil.between(DateUtil.date(Long.parseLong(timestamp) * 1000), DateUtil.date(), DateUnit.SECOND) > NONCE_STR_TIMEOUT_SECONDS) {
    thrownew BusinessException("invalid timestamp");
    }
    // 判斷該使用者的nonceStr參數是否已經在redis中(防止短時間內的重放攻擊)
    Boolean haveNonceStr = redisTemplate.hasKey(nonceStr);
    if (StrUtil.isEmpty(nonceStr) || Objects.isNull(haveNonceStr) || haveNonceStr) {
    thrownew BusinessException("invalid nonceStr");
    }
    // 對請求頭參數進行簽名
    if (StrUtil.isEmpty(signature) || !Objects.equals(signature, this.signature(timestamp, nonceStr, request))) {
    thrownew BusinessException("invalid signature");
    }
    // 將本次使用者請求的nonceStr參數存到redis中設定xx秒後自動刪除
    redisTemplate.opsForValue().set(nonceStr, nonceStr, NONCE_STR_TIMEOUT_SECONDS, TimeUnit.SECONDS);
    returntrue;
    }
    private String signature(String timestamp, String nonceStr, HttpServletRequest request)throws UnsupportedEncodingException {
    Map<String, Object> params = new HashMap<>(16);
    Enumeration<String> enumeration = request.getParameterNames();
    if (enumeration.hasMoreElements()) {
    String name = enumeration.nextElement();
    String value = request.getParameter(name);
    params.put(name, URLEncoder.encode(value, CommonConstants.UTF_8));
    }
    String qs = String.format("%s×tamp=%s&nonceStr=%s&key=%s"this.sortQueryParamString(params), timestamp, nonceStr, key);
    log.info("qs:{}", qs);
    String sign = SecureUtil.md5(qs).toLowerCase();
    log.info("sign:{}", sign);
    return sign;
    }
    /**
    * 按照字母順序進行升序排序
    *
    @param params 請求參數 。註意請求參數中不能包含key
    @return 排序後結果
    */

    private String sortQueryParamString(Map<String, Object> params){
    List<String> listKeys = Lists.newArrayList(params.keySet());
    Collections.sort(listKeys);
    StrBuilder content = StrBuilder.create();
    for (String param : listKeys) {
    content.append(param).append("=").append(params.get(param).toString()).append("&");
    }
    if (content.length() > 0) {
    return content.subString(0, content.length() - 1);
    }
    return content.toString();
    }
    }







    註冊攔截器指定攔截介面

    對敏感數據進行加密傳輸

    使用TLS(傳輸層安全)協定可以保證通訊過程中的數據加密和完整性。以下是一些基本步驟:

  • 在伺服器上配置TLS證書(包括公鑰和私鑰)。

  • 客戶端和伺服器之間建立TLS連線。客戶端向伺服器發送HTTPS請求。

  • 在TLS握手期間,客戶端和伺服器協商加密演算法和金鑰交換方法。

  • 握手成功後,客戶端和伺服器之間的所有數據傳輸都會經過加密處理。

  • 具體的實作取決於所使用的程式語言和框架。以下是使用Java的範例程式碼,演示如何使用TLS進行加密傳輸:

    // 建立SSLContext物件
    SSLContext sslContext = SSLContext.getInstance("TLS");
    // 初始化SSLContext,載入證書和私鑰
    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(new FileInputStream("keystore.jks"), "password".toCharArray());
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, "password".toCharArray());
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore);
    sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
    // 建立HttpsURLConnection連線
    URL url = new URL("https://api.example.com/endpoint");
    HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
    connection.setSSLSocketFactory(sslContext.getSocketFactory());
    // 設定其他請求參數、發送請求、處理響應等




    這段程式碼中,我們建立了一個SSLContext物件並初始化它,載入了伺服器的證書和私鑰。然後,透過 HttpsURLConnection 物件,設定了TLS的安全套接字工廠,並與指定的URL建立了HTTPS連線。

    請註意,你需要將實際的證書和私鑰檔(通常是.jks格式)替換為真實的檔路徑,並提供正確的密碼。

    以上程式碼只是一個簡單的範例,實際部署時可能需要根據具體要求進行更多配置。確保在計畫中遵循最佳實踐和安全建議,並與相應的開發和運維團隊合作,以確保三方介面的安全性。

    AK和SK生成方案

    參考各大雲服務廠商如何獲取aksk:

    https://blog.csdn.net/weixin_43460193/article/details/130242478

    開發一個三方介面,並提供給客戶使用,可以考慮以下方法來生成AK(Access Key)和SK(Secret Key):

    設計API金鑰管理系統:

  • 建立一個API金鑰管理系統,用於生成和管理AK和SK。這個系統可以是一個獨立的伺服器套用或與你的主套用整合在一起。

  • 生成AK和SK:

  • 在API金鑰管理系統中,為每個客戶生成唯一的AK和SK。

  • AK通常是一個公開的識別元,用於標識客戶的身份。可以使用隨機字串、UUID等方式生成。

  • SK是一個保密的私鑰,用於生成身份驗證簽名和加密存取令牌。可以使用隨機字串、哈希函式等方式生成,並確保其足夠安全。

  • * 儲存和管理AK和SK:

  • 將生成的AK和SK儲存在資料庫或其他持久化儲存中,並與客戶的其他相關資訊關聯起來。

  • 需要實施適當的許可權控制和安全措施,以確保只有授權的使用者可以存取和管理AK和SK。

  • 可以考慮對SK進行加密處理,以增加安全性。

  • 提供API金鑰分發機制:

  • 客戶可以透過你提供的界面、API或者自助註冊流程來獲取他們的AK和SK。

  • 在分發過程中,確保以安全的方式將AK和SK傳遞給客戶。例如,使用加密連線或其他安全通道進行傳輸。

  • 安全性和最佳實踐:

  • 強烈建議對API金鑰管理系統進行安全審計,並根據最佳實踐來保護和管理AK和SK。

  • 定期輪換AK和SK,以增加安全性並降低潛在風險。

  • 在設計介面時,使用AK和SK進行身份驗證和許可權控制,以防止未經授權的存取。

  • 請註意,上述步驟提供了一般性的指導,具體實作可能因你的應用程式需求、技術棧和安全策略而有所不同。確保遵循安全最佳實踐,並參考相關的安全文件和建議,以確保生成的AK和SK的安全性和可靠性。

    CREATETABLE api_credentials (
    idINT AUTO_INCREMENT PRIMARY KEY,
    app_id VARCHAR(255NOTNULL,
    access_key VARCHAR(255NOTNULL,
    secret_key VARCHAR(255NOTNULL,
    valid_from DATETIME NOTNULL,
    valid_to DATETIME NOTNULL,
    enabled TINYINT(1NOTNULLDEFAULT1,
    allowed_endpoints VARCHAR(255),
    created_at TIMESTAMPDEFAULTCURRENT_TIMESTAMP
    );

    這個表包含以下欄位:

  • id: 主鍵,自增的唯一識別元。

  • app_id: 應用程式ID或識別元,用於關聯AKSK與特定應用程式。

  • access_key: 存取金鑰(AK),用於標識客戶身份。

  • secret_key: 秘密金鑰(SK),用於生成簽名和進行身份驗證。

  • valid_from: AKSK有效期起始時間。

  • valid_to: AKSK有效期結束時間。

  • enabled: 是否啟用該AKSK,1表示啟用,0表示禁用。

  • allowed_endpoints: 逗號分隔的允許存取的介面/端點列表。

  • created_at: 記錄建立時間。

  • 在實際使用中,你可能需要根據具體需求對欄位進行調整或添加索引以提高效能。此外,還可以考慮添加其他欄位來滿足你的應用程式的需求,例如描述、所屬使用者等。

    請註意,具體的設計可能會因你的應用程式需求和使用場景而有所不同。確保在實施前仔細考慮你的業務要求,並遵循良好的資料庫設計原則和最佳實踐。

    API介面設計補充

    1.使用POST作為介面請求方式

    一般呼叫介面最常用的兩種方式就是GET和POST。兩者的區別也很明顯,GET請求會將參數暴露在瀏覽器URL中,而且對長度也有限制。為了更高的安全性,所有介面都采用POST方式請求。

    2.客戶端IP白名單

    ip白名單是指將介面的存取許可權對部份ip進行開放來避免其他ip進行存取攻擊。

  • 設定ip白名單缺點就是當你的客戶端進行遷移後,就需要重新聯系服務提供者添加新的ip白名單。

  • 設定ip白名單的方式很多,除了傳統的防火墻之外,spring cloud alibaba提供的元件sentinel也支持白名單設定。

  • 為了降低api的復雜度,推薦使用防火墻規則進行白名單設定。

  • 3. 單個介面針對ip限流

    限流是為了更好的維護系統穩定性。

    使用redis進行介面呼叫次數統計,ip+介面地址作為key,存取次數作為value,每次請求value+1,設定過期時長來限制介面的呼叫頻率。

    4. 記錄介面請求日誌

    記錄請求日誌,快速定位異常請求位置,排查問題原因。(如:用aop來全域處理介面請求)

    5. 敏感數據脫敏

    在介面呼叫過程中,可能會涉及到訂單號等敏感數據,這類數據通常需要脫敏處理

    最常用的方式就是加密。加密方式使用安全性比較高的RSA非對稱加密。 非對稱加密演算法有兩個金鑰,這兩個金鑰完全不同但又完全匹配。只有使用匹配的一對公鑰和私鑰,才能完成對明文的加密和解密過程。

    6.冪等性問題

    冪等性是指: 任意多次請求的執行結果和一次請求的執行結果所產生的影響相同。

  • 說的直白一點就是查詢操作無論查詢多少次都不會影響數據本身,因此查詢操作本身就是冪等的。

  • 但是新增操作,每執行一次資料庫就會發生變化,所以它是非冪等的。

  • 冪等問題的解決有很多思路,這裏講一種比較嚴謹的。

  • 提供一個生成隨機數的介面,隨機數全域唯一。呼叫介面的時候帶入隨機數。

  • 第一次呼叫,業務處理成功後,將隨機數作為key,操作結果作為value,存入redis,同時設定過期時長。

  • 第二次呼叫,查詢redis,如果key存在,則證明是重復送出,直接返回錯誤。

  • 7.版本控制

    一套成熟的API文件,一旦釋出是不允許隨意修改介面的。這時候如果想新增或者修改介面,就需要加入版本控制,版本號可以是整數型別,也可以是浮點數型別。

    一般介面地址都會帶上版本號, http://ip:port//v1/list , http://ip:port//v2/list

    8.響應狀態碼規範

    一個牛逼的API,還需要提供簡單明了的響應值,根據狀態碼就可以大概知道問題所在。我們采用http的狀態碼進行數據封裝,例如200表示請求成功,4xx表示客戶端錯誤,5xx表示伺服器內部發生錯誤。

    狀態碼設計參考如下:

    publicenum CodeEnum {// 根據業務需求進行添加
    SUCCESS(200"處理成功"),ERROR_PATH(404"請求地址錯誤"),
    ERROR_SERVER(505"伺服器內部發生錯誤");
    privateint code;
    private String message;
    CodeEnum(int code, String message) {
    this.code = code;
    this.message = message;
    }
    publicintgetCode(){
    return code;
    }
    publicvoidsetCode(int code){
    this.code = code;
    }
    public String getMessage(){
    return message;
    }
    publicvoidsetMessage(String message){
    this.message = message;
    }
    }




    9.統一響應數據格式

    為了方便給客戶端響應,響應數據會包含三個內容, 狀態碼(code) , 資訊描述(message) , 響應數據(data) 。客戶端根據狀態碼及資訊描述可快速知道介面,如果狀態碼返回成功,再開始處理數據。

    public classResultimplementsSerializable{
    privatestaticfinallong serialVersionUID = 793034041048451317L;
    privateint code;
    private String message;
    private Object data = null;
    publicintgetCode(){
    return code;
    }
    publicvoidsetCode(int code){
    this.code = code;
    }
    public String getMessage(){
    return message;
    }
    publicvoidsetMessage(String message){
    this.message = message;
    }
    public Object getData(){
    return data;
    }
    /** * 放入響應列舉 */
    public Result fillCode(CodeEnum codeEnum){
    this.setCode(codeEnum.getCode());
    this.setMessage(codeEnum.getMessage());
    returnthis;
    }
    /** * 放入響應碼及資訊 */
    public Result fillCode(int code, String message){
    this.setCode(code);
    this.setMessage(message);
    returnthis;
    }
    /** * 處理成功,放入自訂業務數據集合 */
    public Result fillData(Object data){
    this.setCode(CodeEnum.SUCCESS.getCode());
    this.setMessage(CodeEnum.SUCCESS.getMessage());
    this.data = data;
    returnthis;
    }
    }









    10.介面文件

    一個好的API還少不了一個優秀的介面文件。介面文件的可讀性非常重要,雖然很多程式設計師都不喜歡寫文件,而且不喜歡別人不寫文件。為了不增加程式設計師的壓力,推薦使用swagger2或其他介面管理工具,透過簡單配置,就可以在開發中測試介面的環通度,上線後也可以生成離線文件用於管理API

    11.生成簽名sign的詳細步驟

    結合案例詳細說明怎麽生成簽名signature(寫完上面的部落格後,得出的感悟)

    第1步: 將所有參數(註意是所有參數,包括appId,timeStamp,nonce),除去sign本身,以及值是空的參數,按key名升序排序儲存。

    第2步: 然後把排序後的參數按 key1value1key2value2…keyXvalueX的方式拼接成一個字串。

    這裏的參數和值必須是傳輸參數的原始值,不能是經過處理的,如不能將"轉成」後再拼接)

    第3步: 把分配給呼叫方的金鑰secret拼接在第2步得到的字串最後面。

    即: key1value1key2value2…keyXvalueX + secret

    第4步: 計算第3步字串的md5值(32位元),然後轉成大寫,最終得到的字串作為簽名sign。

    即: Md5(key1value1key2value2…keyXvalueX + secret) 轉大寫

    舉例:

    假設傳輸的數據是

    http://www.xxx.com/openApi?sign=sign_value&k1=v1&k2=v2&method=cancel&k3=&kX=vX

    請求頭是

    appId:zs001timeStamp:1612691221000sign:2B42AAED20E4B2D5BA389F7C344FE91Bnonce:1234567890

    實際情況最好是透過post方式發送,其中sign參數對應的 sign_value 就是簽名的值。

    第一步:拼接字串。

    首先去除sign參數本身,然後去除值是空的參數k3,剩下 appId=zs001&timeStamp=1612691221000&nonce=1234567890&k1=v1&k2=v2&&method=cancel&kX=vX ,然後按參數名字元升序排序, appId=zs001&k1=v1&k2=v2&kX=vX&method=cancel&nonce=1234567890&timeStamp=1612691221000

    第二步:將參數名和值的拼接

    appIdzs001k1v1k2v2kXvXmethodcancelnonce1234567890timeStamp1612691221000

    第三步:在上面拼接得到的字串前加上金鑰secret

    假設是miyao,得到新的字串 appIdzs001k1v1k2v2kXvXmethodcancelnonce1234567890timeStamp1612691221000miyao

    第四步:然後將這個字串進行md5計算

    假設得到的是abcdef,然後轉為大寫,得到 ABCDEF 這個值作為簽名sign

    註意,計算md5之前呼叫方需確保簽名加密字串編碼與提供方一致,如統一使用utf-8編碼或者GBK編碼,如果編碼方式不一致則計算出來的簽名會校驗失敗。

    上面說的請求錄音可拼可不拼接,主要還是為了增強簽名的復雜性

    12.1.什麽是token?

    Token是什麽?

    token即 存取令牌access token,用於介面中標識介面呼叫者的身份、憑證,減少使用者名稱和密碼的傳輸次數。 一般情況下客戶端(介面呼叫方)需要先向伺服器端申請一個介面呼叫的帳號,伺服器會給出一個appId和一個appSecret(appSecret用於參數簽名使用)

    註意appSecret保存到客戶端,需要做一些安全處理,防止泄露。

    Token的值一般是UUID,伺服端生成Token後需要將token做為key,將一些和token關聯的資訊作為value保存到緩存伺服器中(redis),當一個請求過來後,伺服器就去緩存伺服器中查詢這個Token是否存在,存在則呼叫介面,不存在返回介面錯誤,一般透過攔截器或者過濾器來實作。

    Token分為兩種

  • API Token(介面令牌): 用於存取不需要使用者登入的介面,如登入、註冊、一些基本數據的獲取等。獲取介面令牌需要拿appId、timestamp和sign來換,sign=加密(參數1+…+參數n+timestamp+key)

  • USER Token(使用者令牌): 用於存取需要使用者登入之後的介面,如:獲取我的基本資訊、保存、修改、刪除等操作。獲取使用者令牌需要拿使用者名稱和密碼來換

  • 12.2.Token+簽名(有使用者狀態的介面簽名)

    上面講的介面簽名方式都是無狀態的,在APP開放API介面的設計中,由於大多數介面涉及到使用者的個人資訊以及產品的敏感數據,所以要對這些介面進行身份驗證,為了安全起見讓使用者暴露的明文密碼次數越少越好,然而客戶端與伺服器的互動在請求之間是無狀態的,也就是說,當涉及到使用者狀態時,每次請求都要帶上身份驗證資訊(令牌token)。

    1.Token身份驗證

  • 使用者登入向伺服器提供認證資訊(如帳號和密碼),伺服器驗證成功後返回Token給客戶端;

  • 客戶端將Token緩存在本地,後續每次發起請求時,都要攜帶此Token;

  • 伺服端檢查Token的有效性,有效則放行,無效(Token錯誤或過期)則拒絕。

  • 弊端:Token被劫持,偽造請求和篡改參數。

    2.Token+簽名驗證

    與上面介面簽名規則一樣,為客戶端分配appSecret(金鑰,用於介面加密,不參與傳輸),將appSecret和所有請求參陣列合成一個字串,根據簽名演算法生成簽名值,發送請求時將簽名值一起發送給伺服器驗證。

    這樣,即使Token被劫持,對方不知道appSecret和簽名演算法,就無法偽造請求和篡改參數,並且有了token後也能正確的獲取到使用者的狀態

    登陸和結束請求

    後續請求

    客戶端: 與上面介面簽名規則一樣類似,把appId改為token即可。

    如喜歡本文,請點選右上角,把文章分享到朋友圈
    如有想了解學習的技術點,請留言給若飛安排分享

    因公眾號更改推播規則,請點「在看」並加「星標」 第一時間獲取精彩技術分享

    ·END·

    相關閱讀:

    作者:jimmyhus

    來源:juejin.cn/post/7268667384440504360

    版權申明:內容來源網路,僅供學習研究,版權歸原創者所有。如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝!

    架構師

    我們都是架構師!

    關註 架構師(JiaGouX),添加「星標」

    獲取每天技術幹貨,一起成為牛逼架構師

    技術群請 加若飛: 1321113940 進架構師群

    投稿、合作、版權等信箱: [email protected]