App開放介面api安全性—Token簽名sign的設計與實現
前言
在app開放介面api的設計中,避免不了的就是安全性問題,因為大多數介面涉及到使用者的個人資訊以及一些敏感的資料,所以對這些介面需要進行身份的認證,那麼這就需要使用者提供一些資訊,比如使用者名稱密碼等,但是為了安全起見讓使用者暴露的明文密碼次數越少越好,我們一般在web專案中,大多數採用儲存的session中,然後在存一份到cookie中,來保持使用者的回話有效性。但是在app提供的開放介面中,後端伺服器在使用者登入後如何去驗證和維護使用者的登陸有效性呢,以下是參考專案中設計的解決方案,其原理和大多數開放介面安全驗證一樣,如淘寶的開放介面token驗證,微信開發平臺token驗證都是同理。
簽名設計
對於敏感的api介面,需使用https協議
https是在http超文字傳輸協議加入SSL層,它在網路間通訊是加密的,所以需要加密證書。
https協議需要ca證書,一般需要交費。
簽名的設計
原理:使用者登入後向伺服器提供使用者認證資訊(如賬戶和密碼),伺服器認證完後給客戶端返回一個Token令牌,使用者再次獲取資訊時,帶上此令牌,如果令牌正取,則返回資料。對於獲取Token資訊後,訪問使用者相關介面,客戶端請求的url需要帶上如下引數:
時間戳:timestamp
Token令牌:token
然後將所有使用者請求的引數按照字母排序(包括timestamp,token),然後更具MD5加密(可以加點鹽),全部大寫,生成sign簽名,這就是所說的url簽名演算法。然後登陸後每次呼叫使用者資訊時,帶上sign,timestamp,token引數。
例如:原請求https://www.andy.cn/api/user/update/info.shtml?city=北京 (post和get都一樣,對所有引數排序加密)
加上時間戳和token
https://www.andy.cn/api/user/update/info.shtml?city=北京×tamp=12445323134&token=wefkfjdskfjewfjkjfdfnc
然後更具url引數生成sign
最終的請求如
https://www.andy.cn/api/user/update/info.shtml?city=北京×tamp=12445323134&token=wefkfjdskfjewfjkjfdfnc&sign=FDK2434JKJFD334FDF2
其最終的原理是減小明文的暴露次數;保證資料安全的訪問。
具體實現如下:
1. api請求客戶端想伺服器端一次傳送用使用者認證資訊(使用者名稱和密碼),伺服器端請求到改請求後,驗證使用者資訊是否正確。
如果正確:則返回一個唯一不重複的字串(一般為UUID),然後在Redis(任意快取伺服器)中維護Token----Uid的使用者資訊關係,以便其他api對token的校驗。
如果錯誤:則返回錯誤碼。
2.伺服器設計一個url請求攔截規則
(1)判斷是否包含timestamp,token,sign引數,如果不含有返回錯誤碼。
(2)判斷伺服器接到請求的時間和引數中的時間戳是否相差很長一段時間(時間自定義如半個小時),如果超過則說明該 url已經過期(如果url被盜,他改變了時間戳,但是會導致sign簽名不相等)。
(3)判斷token是否有效,根據請求過來的token,查詢redis快取中的uid,如果獲取不到這說明該token已過期。
(4)根據使用者請求的url引數,伺服器端按照同樣的規則生成sign簽名,對比簽名看是否相等,相等則放行。(自然url簽名 也無法100%保證其安全,也可以通過公鑰AES對資料和url加密,但這樣如果無法確保公鑰丟失,所以簽名只是很大程 度上保證安全)。
(5)此url攔截只需對獲取身份認證的url放行(如登陸url),剩餘所有的url都需攔截。
3.Token和Uid關係維護
對於使用者登入我們需要建立token--uid的關係,使用者退出時需要需刪除token--uid的關係。
簽名實現
獲取全部請求引數
String sign = request.getParameter("sign");
Enumeration<?> pNames = request.getParameterNames();
Map<String, Object> params = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if("sign".equals(pName))continue;
Object pValue = request.getParameter(pName);
params.put(pName, pValue);
}
生成簽名
public static String createSign(Map<String, String> params, boolean encode)
throws UnsupportedEncodingException {
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
boolean first = true;
for (Object key : keys) {
if (first) {
first = false;
} else {
temp.append("&");
}
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = String.valueOf(value);
}
if (encode) {
temp.append(URLEncoder.encode(valueString, "UTF-8"));
} else {
temp.append(valueString);
}
}
return MD5Utils.getMD5(temp.toString()).toUpperCase();
}