目前越來(lái)越多的接口請(qǐng)求使用了簽名的方式,整個(gè)思路就是我們和后臺(tái)約定一個(gè)對(duì)參數(shù)的簽名方式,簽名完成以后在所有參數(shù)的后面多一個(gè)本地完成的簽名字符串作為參數(shù)一起傳給后臺(tái),后臺(tái)通過(guò)同樣的簽名方式也生成簽名,跟我們傳給后臺(tái)的這個(gè)簽名參數(shù)做對(duì)比,如果一致則代表參數(shù)沒(méi)有被修改,一定程度保證了安全性。最近公司安全部門讓我們做一下請(qǐng)求參數(shù)簽名,個(gè)人覺(jué)得這部分基本都是這樣的用法,所以總結(jié)出來(lái):
先說(shuō)一下加密簽名的這個(gè)流程:
1: 對(duì)所有外層參數(shù)按照參數(shù)的ASCII從小到大排序
2:對(duì)排序后的參數(shù)列表去除為空的參數(shù)并用這個(gè)進(jìn)行MD5加密,然后再轉(zhuǎn)成大寫
3:用第一步排序后的參數(shù)列表拼上第二步生成的簽名作為一個(gè)json字符串
因?yàn)槲覀兒笈_(tái)要求的使用RequestBody的形式請(qǐng)求,輸入的是json字符串,所以我寫了一個(gè)工具類先是把這個(gè)json字符串轉(zhuǎn)成Map,然后再排序。方法如下:
/**
* 將RequestBody轉(zhuǎn)成map并按照key的字母順序進(jìn)行排序得出新的map
*
* @param json
* @return
*/
public static Map<String, Object> sortParamsMap(String json) {
Map<String, Object> objectMap = GsonUtils.jsonToMap(json);
Map<String, Object> newMap = sortMapByKey(objectMap);
return newMap;
}
/**
* 使用 Map按key進(jìn)行排序
*
* @param map
* @return
*/
public static Map<String, Object> sortMapByKey(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> sortMap = new TreeMap<>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
static class MapKeyComparator implements Comparator<String> {
@Override
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
排序好了以后我們需要去除Map中value為空的項(xiàng),用不為空的項(xiàng)再次轉(zhuǎn)成json字符串然后MD5加密:
這里涉及到一個(gè)Map遍歷刪除的方法,需要注意的是next方法在程序的一次流程中只能被調(diào)用一次,否則它會(huì)去找下一項(xiàng)可能導(dǎo)致判斷的有的判斷不準(zhǔn)確甚至如果下一項(xiàng)為空可能會(huì)導(dǎo)致報(bào)錯(cuò):NoSuchElementException,所以我們可以這樣使用:
Map.Entry<String,Object> entry = iterator.next();
Object value = entry.getValue();
/**
* 帶簽名的傳輸參數(shù)
*
* @param json
* @return
*/
public static String paramsJson(String json) {
Map<String, Object> sortMap = = GsonUtils.jsonToMap(json);
Map<String, Object> newMap = sortParamsMap(json);
Iterator<Map.Entry<String, Object>> iterator = newMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String,Object> entry = iterator.next();
Object value = entry.getValue();
if (value == null || value.equals("")) {
iterator.remove();
}
}
String sign = getSign(newMap);
sortMap.put("sign", sign);
String result = GsonUtils.ObjectToJsonStr(sortMap);
return result;
}
/**
* @param newMap
* @return 對(duì)排序后的參數(shù)進(jìn)行MD5加密生成大寫字母的簽名
*/
public static String getSign(Map<String, Object> newMap) {
String paramsStr = GsonUtils.ObjectToJsonStr(newMap);
return MD5Utils.getMd5Value(paramsStr).toUpperCase();
}
這里一開始使用的是Gson的fromJson方法把json轉(zhuǎn)成map,可是出現(xiàn)了一個(gè)解析的問(wèn)題,這個(gè)方法會(huì)把int的比如1轉(zhuǎn)成double的1.0,這樣后面MD5后就不一樣了,所以就找度娘怎么解決,找了半天試了有四五種,終于讓我找到一個(gè)可以的方案:
解析這塊兒所有的代碼如下:
public static Map<String, Object> jsonToMap(String json) {
Map<String,Object> map = fromJson(json,new TypeToken<Map<String, Object>>(){});
return map;
}
/**
* json字符串轉(zhuǎn)list或者map
*
* @param json
* @param typeToken
* @return
*/
public static <T> T fromJson(String json, TypeToken<T> typeToken) {
Gson gson = new GsonBuilder()
/**
* 重寫map的反序列化
*/
.registerTypeAdapter(new TypeToken<Map<String, Object>>() {
}.getType(), new GsonTypeAdapter()).create();
//MapTypeAdapter是繼承了TypeAdapter類,并單獨(dú)處理Map類型的反序列化。注意:目前只綁定了Map類型,其子類(HashMap)的處理沒(méi)有變化。具體代碼見本文最后或GitHub(發(fā)布后會(huì)給出地址)。
return gson.fromJson(json, typeToken.getType());
}
//重點(diǎn)是這里
public class GsonTypeAdapter extends TypeAdapter<Object> {
@Override
public Object read(JsonReader in) throws IOException {
JsonToken token = in.peek();
switch (token) {
case BEGIN_ARRAY:
List<Object> list = new ArrayList<Object>();
in.beginArray();
while (in.hasNext()) {
list.add(read(in));
}
in.endArray();
return list;
case BEGIN_OBJECT:
Map<String, Object> map = new LinkedTreeMap<String, Object>();
in.beginObject();
while (in.hasNext()) {
map.put(in.nextName(), read(in));
}
in.endObject();
return map;
case STRING:
return in.nextString();
case NUMBER:
/**
* 改寫數(shù)字的處理邏輯,將數(shù)字值分為整型與浮點(diǎn)型。
*/
double dbNum = in.nextDouble();
// 數(shù)字超過(guò)long的最大值,返回浮點(diǎn)類型
if (dbNum > Long.MAX_VALUE) {
return dbNum;
}
// 判斷數(shù)字是否為整數(shù)值
long lngNum = (long) dbNum;
if (dbNum == lngNum) {
return lngNum;
} else {
return dbNum;
}
case BOOLEAN:
return in.nextBoolean();
case NULL:
in.nextNull();
return null;
default:
throw new IllegalStateException();
}
}
@Override
public void write(JsonWriter out, Object value) throws IOException {
// 序列化無(wú)需實(shí)現(xiàn)
}
}
另外還需要注意的一點(diǎn)是為了保證我們客戶端的簽名和后臺(tái)的簽名一致,務(wù)必要保證MD5的編碼格式跟我們的保持一致,我這里的MD5方法提供出來(lái):
/**
* 32位MD5加密方法
* 16位小寫加密只需getMd5Value("xxx").substring(8, 24);即可
*
* @param sSecret
* @return
*/
public static String getMd5Value(String sSecret) {
try {
MessageDigest bmd5 = MessageDigest.getInstance("MD5");
bmd5.update(sSecret.getBytes("UTF-8"));
int i;
StringBuffer buf = new StringBuffer();
// 加密
byte[] b = bmd5.digest();
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0)
i += 256;
if (i < 16)
buf.append("0");
buf.append(Integer.toHexString(i));
}
return buf.toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
下面是簽名的類,包含上面涉及到的所有簽名方法:
/**
@author : zhengangyao
@e-mail : 139745815@ qq.com
@date : 2018/12/1718:19
@desc : 請(qǐng)求參數(shù)簽名工具類
-
@version: 1.1.0
*/
public class SignUtil {/**
- 帶簽名的傳輸參數(shù)
- @param json
- @return
*/
public static String paramsJson(String json) {
Map<String, Object> sortMap = = GsonUtils.jsonToMap(json);
Map<String, Object> newMap = sortParamsMap(json);
Iterator<Map.Entry<String, Object>> iterator = newMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String,Object> entry = iterator.next();
Object value = entry.getValue();
if (value == null || value.equals("")) {
iterator.remove();
}
}
String sign = getSign(newMap);
sortMap.put("sign", sign);
String result = GsonUtils.ObjectToJsonStr(sortMap);
return result;
}
/**
- @param newMap
- @return 對(duì)排序后的參數(shù)進(jìn)行MD5加密生成大寫字母的簽名
*/
public static String getSign(Map<String, Object> newMap) {
String paramsStr = GsonUtils.ObjectToJsonStr(newMap);
return MD5Utils.getMd5Value(paramsStr).toUpperCase();
}
/**
- 將RequestBody轉(zhuǎn)成map并按照key的字母順序進(jìn)行排序得出新的map
- @param json
- @return
*/
public static Map<String, Object> sortParamsMap(String json) {
Map<String, Object> objectMap = GsonUtils.jsonToMap(json);
Map<String, Object> newMap = sortMapByKey(objectMap);
return newMap;
}
/**
- 使用 Map按key進(jìn)行排序
- @param map
- @return
*/
public static Map<String, Object> sortMapByKey(Map<String, Object> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> sortMap = new TreeMap<>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
static class MapKeyComparator implements Comparator<String> {
@Override public int compare(String str1, String str2) { return str1.compareTo(str2); }}
}