電商平臺-安全設計與架構
說明:Java開源生鮮電商平臺是一個B2B的生鮮電商平臺,由於是APP的一種模式,所以安全方面一般會思考以下幾個維度:
1.資料安全。
2.傳輸安全。
App與伺服器的通訊介面如何設計得好,需要考慮的地方挺多的,在此根據我的一些經驗做一些總結分享,旨在拋磚引玉。
安全機制的設計
現在,大部分App的介面都採用RESTful架構,RESTFul最重要的一個設計原則就是,客戶端與伺服器的互動在請求之間是無狀態的,也就是說,當涉及到使用者狀態時,每次請求都要帶上身份驗證資訊。實現上,大部分都採用token的認證方式,一般流程是:
- 使用者用密碼登入成功後,伺服器返回token給客戶端;
- 客戶端將token儲存在本地,發起後續的相關請求時,將token發回給伺服器;
- 伺服器檢查token的有效性,有效則返回資料,若無效,分兩種情況:
- token錯誤,這時需要使用者重新登入,獲取正確的token
- token過期,這時客戶端需要再發起一次認證請求,獲取新的token
然而,此種驗證方式存在一個安全性問題:當登入介面被劫持時,黑客就獲取到了使用者密碼和token,後續則可以對該使用者做任何事情了。使用者只有修改密碼才能奪回控制權。
如何優化呢?第一種解決方案是採用HTTPS。HTTPS在HTTP的基礎上添加了SSL安全協議,自動對資料進行了壓縮加密,在一定程式可以防止監聽、防止劫持、防止重發,安全性可以提高很多。不過,SSL也不是絕對安全的,也存在被劫持的可能。另外,伺服器對HTTPS的配置相對有點複雜,還需要到CA申請證書,而且一般還是收費的。而且,HTTPS效率也比較低。一般,只有安全要求比較高的系統才會採用HTTPS,比如銀行。而大部分對安全要求沒那麼高的App還是採用HTTP的方式。
我們目前的做法是給每個介面都添加簽名。給客戶端分配一個金鑰,每次請求介面時,將金鑰和所有引數組合成源串,根據簽名演算法生成簽名值,傳送請求時將簽名一起傳送給伺服器驗證。類似的實現可參考OAuth1.0的簽名演算法。這樣,黑客不知道金鑰,不知道簽名演算法,就算攔截到登入介面,後續請求也無法成功操作。不過,因為簽名演算法比較麻煩,而且容易出錯,只適合對內的介面。如果你們的介面屬於開放的API,則不太適合這種簽名認證的方式了,建議還是使用OAuth2.0的認證機制。
我們也給每個端分配一個appKey,比如Android、iOS、微信三端,每個端分別分配一個appKey和一個金鑰。沒有傳appKey的請求將報錯,傳錯了appKey的請求也將報錯。這樣,安全性方面又加多了一層防禦,同時也方便對不同端做一些不同的處理策略。
另外,現在越來越多App取消了密碼登入,而採用手機號+簡訊驗證碼的登入方式,我在當前的電商專案中也採用了這種登入方式。這種登入方式有幾種好處:
- 不需要註冊,不需要修改密碼,也不需要因為忘記密碼而重置密碼的操作了;
- 使用者不再需要記住密碼了,也不怕密碼洩露的問題了;
- 相對於密碼登入其安全性明顯提高了。
介面資料的設計
介面的資料一般都採用JSON格式進行傳輸,不過,需要注意的是,JSON的值只有六種資料型別:
- Number:整數或浮點數
- String:字串
- Boolean:true 或 false
- Array:陣列包含在方括號[]中
- Object:物件包含在大括號{}中
- Null:空型別
所以,傳輸的資料型別不能超過這六種資料型別。以前,我們曾經試過傳輸Date型別,它會轉為類似於"2016年1月7日 09時17分42秒 GMT+08:00"這樣的字串,這在轉換時會產生問題,不同的解析庫解析方式可能不同,有的可能會轉亂,有的可能直接異常了。要避免出錯,必須做特殊處理,自己手動去做解析。為了根除這種問題,最好的解決方案是用毫秒數表示日期。
另外,以前的專案中還出現過字串的"true"和"false",或者字串的數字,甚至還出現過字串的"null",導致解析錯誤,尤其是"null",導致App奔潰,後來查了好久才查出來是該問題導致的。這都是因為服務端對資料沒處理好,導致有些資料轉為了字串。所以,在客戶端,也不能完全信任服務端傳回的資料都是對的,需要對所有異常情況都做相應處理。
伺服器返回的資料結構,一般為:
{ "code":"200", "message": "請求成功", "Object": { key1: value1, key2: value2, ... } }
- code: 返回碼,200表示成功,非200表示各種不同的錯誤
- message: 描述資訊,成功時為"請求成功 ",錯誤時則是錯誤資訊
- object: 成功時返回的資料,型別為
不同錯誤需要定義不同的返回碼,屬於客戶端的錯誤和服務端的錯誤也要區分,比如1XX表示客戶端的錯誤,2XX表示服務端的錯誤。這裡舉幾個例子:
- 200:成功
- 201:請求錯誤
- 202:缺少appKey
- 203:缺少簽名
- 204:缺少引數
- 205:伺服器出錯
- 206:服務不可用
- 207:伺服器正在重啟
錯誤資訊一般有兩種用途:一是客戶端開發人員除錯時看具體是什麼錯誤;二是作為App錯誤提示直接展示給使用者看。主要還是作為App錯誤提示,直接展示給使用者看的。所以,大部分都是簡短的提示資訊。
data欄位只在請求成功時才會有資料返回的。資料型別限定為物件或陣列,當請求需要的資料為單個物件時則傳回物件,當請求需要的資料是列表時,則為某個物件的陣列。這裡需要注意的就是,不要將data傳入字串或數字,即使請求需要的資料只有一個,比如token,那返回的data應該為:
// 正確 data: { token: 123456 } // 錯誤 data: 123456
介面版本的設計
介面不可能一成不變,在不停迭代中,總會發生變化。介面的變化一般會有幾種:
- 資料的變化,比如增加了舊版本不支援的資料型別
- 引數的變化,比如新增了引數
- 介面的廢棄,不再使用該介面了
為了適應這些變化,必須得做介面版本的設計。實現上,一般有兩種做法:
- 每個介面有各自的版本,一般為介面添加個version的引數。
- 整個介面系統有統一的版本,一般在URL中新增版本號,比如http://api.domain.com/v2。
大部分情況下會採用第一種方式,當某一個介面有變動時,在這個介面上疊加版本號,併兼容舊版本。App的新版本開發傳參時則將傳入新版本的version。
如果整個介面系統的根基都發生變動的話,比如微博API,從OAuth1.0升級到OAuth2.0,整個API都進行了升級。
有時候,一個介面的變動還會影響到其他介面,但做的時候不一定能發現。因此,最好還要有一套完善的測試機制保證每次介面變更都能測試到所有相關層面。
寫在最後
關於介面安全的設計以及架構,以及密碼學安全方面等等,我這只是給了一個實際的例子,告訴大家,我是這麼做的,有問題歡迎留言。