API接口的安全性主要是為了保證數據不會被篡改和重復調用,實現方案主要圍繞Token、時間戳和Sign三個機制展開設計。
1. Token授權機制
用戶使用用戶名密碼登錄后服務器給客戶端返回一個Token(必須要保證唯一,可以結合UUID和本地設備標示),並將Token-UserId以鍵值對的形式存放在緩存服務器中(我們是使用Redis),並要設置失效時間。服務端接收到請求后進行Token驗證,如果Token不存在,說明請求無效。Token是客戶端訪問服務端的憑證。
# uuid 是手機設備的唯一標示
String token = UUID.randomUUID().toString() + "_" + uuid;
2. 時間戳超時機制
用戶每次請求都帶上當前時間的時間戳timestamp,服務端接收到timestamp后跟當前時間進行比對,如果時間差大於一定時間(比如30秒),則認為該請求失效。時間戳超時機制是防御重復調用和爬取數據的有效手段。
當然這里需要注意的地方是保證客戶端和服務端的“當前時間”是一致的,我們采取的對齊方式是客戶端第一次連接服務端時請求一個接口獲取服務端的當前時間A1,再和客戶端的當前時間B1做一個差異化計算(A1-B1=AB),得出差異值AB,客戶端再后面的請求中都是傳B1+AB給到服務端。
用戶每次請求都帶上當前時間的時間戳timestamp,服務端接收到timestamp后跟當前時間進行比對,如果時間差大於一定時間(比如30秒),則認為該請求失效。時間戳超時機制是防御重復調用和爬取數據的有效手段。
當然這里需要注意的地方是保證客戶端和服務端的“當前時間”是一致的,我們采取的對齊方式是客戶端第一次連接服務端時請求一個接口獲取服務端的當前時間A1,再和客戶端的當前時間B1做一個差異化計算(A1-B1=AB),得出差異值AB,客戶端再后面的請求中都是傳B1+AB給到服務端。
// timeStamp是客戶端從Header傳過來的值 Long timeStamp = RequestHeaderContext.getInstance().getTimeStamp(); boolean checkTime = checkTime(timeStamp, 30 * 1000); if (!checkTime) { return responseErrorAPISecurity(response); } // checkTime方法 public static boolean checkTime(Long time, Integer variable){ Long currentTimeMillis = System.currentTimeMillis(); Long addTime = currentTimeMillis + variable; Long subTime = currentTimeMillis - variable; if (addTime > time && time > subTime){ return true; } return false; }
3. API簽名機制
將“請求的API參數”+“時間戳”+“鹽”進行MD5算法加密,加密后的數據就是本次請求的簽名signature,服務端接收到請求后以同樣的算法得到簽名,並跟當前的簽名進行比對,如果不一樣,說明參數被更改過,直接返回錯誤標識。簽名機制保證了數據不會被篡改。
將“請求的API參數”+“時間戳”+“鹽”進行MD5算法加密,加密后的數據就是本次請求的簽名signature,服務端接收到請求后以同樣的算法得到簽名,並跟當前的簽名進行比對,如果不一樣,說明參數被更改過,直接返回錯誤標識。簽名機制保證了數據不會被篡改。
// 請求的API參數,如果是再body,則MD5;如果是param,則原字符串 StringBuffer urlSign = new StringBuffer(); if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) { String bodyStr = RequestReaderUtil.ReadAsChars(request); String bodySign = ""; if (!StringUtils.isEmpty(bodyStr)){ bodySign = DigestUtils.md5DigestAsHex((bodyStr).getBytes()); } urlSign = new StringBuffer(bodySign); } else if ("GET".equals(request.getMethod()) || "DELETE".equals(request.getMethod())) { String params = request.getQueryString(); if (params == null){ params = ""; } urlSign = new StringBuffer(params); } // “請求的API參數”+“時間戳”+“鹽”進行MD5算法加密 String sign = DigestUtils.md5DigestAsHex(urlSign.append(timeStamp).append(salt).toString().getBytes()); // signature是客戶端從Header傳過來的值 if (signature.equals(sign)) { return true; } else { return false; }
4. 注意事項
/** * 登錄后由服務端生成並返回 */ private String token; /** * 安全校驗字段(接口參數+時間戳+加鹽:取MD5生成) */ private String signature; /** * 設備唯一標識 */ private String udid; /** * 時間戳,13位,比如:1532942172000 */ private Long timeStamp;
5. 安全保障總結
在以上機制下,
如果有人劫持了請求,並對請求中的參數進行了修改,簽名就無法通過;
如果有人使用已經劫持的URL進行DOS攻擊和爬取數據,那么他也只能最多使用30s;
如果簽名算法都泄露了怎么辦?可能性很小,因為這里的“鹽”值只有我們自己知道。
在以上機制下,
如果有人劫持了請求,並對請求中的參數進行了修改,簽名就無法通過;
如果有人使用已經劫持的URL進行DOS攻擊和爬取數據,那么他也只能最多使用30s;
如果簽名算法都泄露了怎么辦?可能性很小,因為這里的“鹽”值只有我們自己知道。