Java實現(xiàn)API sign簽名校驗的方法詳解
1. 前言
目的:為防止中間人攻擊。
場景:
- 項目內(nèi)部前后端調(diào)用,這種場景只需要做普通參數(shù)的簽名校驗和過期請求校驗,目的是為了防止攻擊者劫持請求 url 后非法請求接口。
- 開放平臺向第三方應(yīng)用提供能力,這種場景除了普通參數(shù)校驗和請求過期校驗外,還要考慮 3d 應(yīng)用的授權(quán)機制,不被授權(quán)的應(yīng)用就算傳入了合法的參數(shù)也不能被允許請求成功。
2. 簽名生成策略
接下來詳述場景 2,其實場景 1 也包含在場景 2 內(nèi)部。
1.舉例請求 url:
http://api.abc.com/a-service/orders?orderType=1001&requestFrom=IOS&pageNum=2&pageSize=10
請求參數(shù)為:
| 參數(shù)名 | 位置 | 備注 | 舉例 |
|---|---|---|---|
| X-Access-Key | header | 客戶端授權(quán)碼,服務(wù)端提供,和 accessSecret 配對(場景 1 無此參數(shù)) | app1 |
| X-Access-Token | header | 當前登錄用戶 token | d7b5808c3f443eb5a496225468c7e4a5 |
| X-UTCTime | header | 當前發(fā)送請求時的時間 | 2022-02-16T09:12:43.083Z |
| X-Random | header | 請求隨機數(shù) | 341be97d9aff90c9978347f66f945b77 |
| orderType | query | 訂單類型 | 1001 |
| requestFrom | query | 訂單來源 | IOS |
| pageNum | query | 分頁參數(shù) | 10 |
| pageSize | query | 分頁參數(shù) | 2 |
2.設(shè)原始參數(shù)為 stringA,stringA 中添加 X-Access-Key、X-UTCTime、X-Random 固定參數(shù),將 stringA 內(nèi)非空參數(shù)值和 header 的參數(shù)按照參數(shù)名 ASCII 碼從小到大排序(字典序),使用 URL 鍵值對的格式(即 key1=value1&key2=value2…)拼接成字符串 stringB。
注意如下規(guī)則:
- 參數(shù)名 ASCII 碼從小到大排序(字典序);
- 如果參數(shù)的值為空不參與簽名;
- 參數(shù)名區(qū)分大小寫;
- 驗證調(diào)用返回或主動通知簽名時,傳送的 sign 參數(shù)不參與簽名,將生成的簽名與該 sign 值作校驗。
// 最終拼接為stringB: orderType=1001X-UTCTime=2022-02-16T09:12:43.083ZpageSize=10X-Access-Key=app1X-Access-Token=d7b5808c3f443eb5a496225468c7e4a5pageNum=2requestFrom=IOSX-Random=341be97d9aff90c9978347f66f945b77
3.在 stringB 最后拼接上 accessSecret (密鑰) 得到 stringC 字符串,并對 stringC 進行 MD5 運算,得到 sign 值。
// 最后拼上accessSecret得到stringC: orderType=1001X-UTCTime=2022-02-16T09:12:43.083ZpageSize=10X-Access-Key=app1X-Access-Token=d7b5808c3f443eb5a496225468c7e4a5pageNum=2requestFrom=IOSX-Random=341be97d9aff90c9978347f66f945b77&accessSecret=192006250b4c09247ec02edce69f6a2d // md5加密得到最終簽名結(jié)果sign: sign=e1a4907ef03adee3fa8d395552814f4e
4.將原始的請求 url 拼接上 sign 形成最終的請求 url。
http://api.abc.com/a-service/orders?orderType=1001&requestFrom=IOS&pageNum=2&pageSize=10&sign=0f5a3cc534961d129a25d52d7ed8d003
5.最終請求 url 如下:
http://api.abc.com/a-service/orders?orderType=1001&requestFrom=IOS&pageNum=2&pageSize=10&sign=0f5a3cc534961d129a25d52d7ed8d003
| 參數(shù)名 | 位置 | 備注 | 舉例 |
|---|---|---|---|
| X-Access-Key | header | 客戶端授權(quán)碼,服務(wù)端提供,和 accessSecret 配對(場景 1 無此參數(shù)) | app1 |
| X-Access-Token | header | 當前登錄用戶 token | d7b5808c3f443eb5a496225468c7e4a5 |
| X-UTCTime | header | 當前發(fā)送請求時的時間 | 2022-02-16T09:12:43.083Z |
| X-Random | header | 請求隨機數(shù) | 341be97d9aff90c9978347f66f945b77 |
| orderType | query | 訂單類型 | 1001 |
| requestFrom | query | 訂單來源 | IOS |
| pageNum | query | 分頁參數(shù) | 10 |
| pageSize | query | 分頁參數(shù) | 2 |
6.服務(wù)端 gateway 同樣做 sign 簽名加密和校驗,如果校驗不通過則說明請求非法,直接拒絕,通過則下發(fā)到業(yè)務(wù)服務(wù)進行正常請求處理。
3. API 簽名算法 Java 實現(xiàn)
public class SignUtil {
/**
* 生成簽名
*
* @param accessSecret accessSecret
* @param url url
* @param headers headers
* @param body post的body體
* @param <T> body體泛型
* @return sign
*/
public static <T> String sign(String accessSecret, String url, Map<String, Object> headers, T body) throws IllegalAccessException {
Map<String, Object> signMap = new HashMap<>();
if (headers != null) {
signMap.putAll(headers);
}
Map<String, Object> paramMap = getUrlParams(url);
if (paramMap != null) {
signMap.putAll(paramMap);
}
Map<String, Object> bodyMap = getBodyParams(body);
if (bodyMap != null) {
signMap.putAll(bodyMap);
}
StringBuffer sb = new StringBuffer();
signMap.forEach((k, v) -> {
sb.append(k).append("=").append(v).append("&");
});
sb.append("accessSecret=").append(accessSecret);
return stringToMD5(sb.toString());
}
private static Map<String, Object> getUrlParams(String url) {
if (StringUtils.isBlank(url) || !url.contains("?")) {
return null;
}
Map<String, Object> paramMap = new HashMap<>();
String params = url.split("\\?")[1];
for (String param : params.split("&")) {
String[] p = param.split("=");
paramMap.put(p[0], p[1]);
}
return paramMap;
}
private static <T> Map<String, Object> getBodyParams(T body) throws IllegalAccessException {
if (body == null) {
return null;
}
Map<String, Object> bodyMap = new HashMap<>();
for (Field field : body.getClass().getDeclaredFields()) {
field.setAccessible(true);
bodyMap.put(field.getName(), field.get(body));
}
return bodyMap;
}
private static String stringToMD5(String plainText) {
byte[] secretBytes = null;
try {
secretBytes = MessageDigest.getInstance("md5").digest(
plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("沒有這個md5算法!");
}
return new BigInteger(1, secretBytes).toString(16);
}
}
4. 測試一下
public class App {
public static void main(String[] args) throws IllegalAccessException {
String url = "http://api.abc.com/a-service/orders?orderType=1001&requestFrom=IOS&pageNum=2&pageSize=10";
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("X-Access-Key", "app1");
headerMap.put("X-Access-Token", "d7b5808c3f443eb5a496225468c7e4a5");
headerMap.put("X-UTCTime", generateDate());
headerMap.put("X-Random", "341be97d9aff90c9978347f66f945b77");
BodyVO body = new BodyVO(100000001L, "yangcan", new Date());
String sign = SignUtil.sign("sdfsdfdsfdsfds", url, headerMap, body);
System.out.println(sign);
}
/**
* 獲取當前時間的UTC格式
*/
private static String generateDate() {
Date now = new Date();
DateFormat format = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.US);
return format.format(now);
}
@Data
@AllArgsConstructor
public static class BodyVO {
private Long ycId;
private String ycName;
private Date ycTime;
}
}
輸出:
sign = 4f52eb34b30129a8d511dc803044086b
到此這篇關(guān)于Java實現(xiàn)API sign簽名校驗的方法詳解的文章就介紹到這了,更多相關(guān)Java API簽名校驗內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何在 Java 中實現(xiàn)一個 redis 緩存服務(wù)
為什么要使用緩存?說到底是為了提高系統(tǒng)的運行速度。將用戶頻繁訪問的內(nèi)容存放在離用戶最近,訪問速度最快的地方,提高用戶的響應(yīng)速度。下面我們來一起深入學(xué)習(xí)一下吧2019-06-06
SpringBoot利用切面注解及反射實現(xiàn)事件監(jiān)聽功能
這篇文章主要介紹了springboot事件監(jiān)聽,通過利用切面、注解、反射實現(xiàn),接下來將對這幾種方式逐一說明,具有很好的參考價值,希望對大家有所幫助2022-07-07
SpringMVC空指針異常NullPointerException解決及原理解析
這篇文章主要介紹了SpringMVC空指針異常NullPointerException解決及原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08
SpringBoot Shiro授權(quán)實現(xiàn)過程解析
這篇文章主要介紹了SpringBoot Shiro授權(quán)實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11

