目前越来越多的接口请求使用了签名的方式,整个思路就是我们和后台约定一个对参数的签名方式,签名完成以后在所有参数的后面多一个本地完成的签名字符串作为参数一起传给后台,后台通过同样的签名方式也生成签名,跟我们传给后台的这个签名参数做对比,如果一致则代表参数没有被修改,一定程度保证了安全性。最近公司安全部门让我们做一下请求参数签名,个人觉得这部分基本都是这样的用法,所以总结出来:
先说一下加密签名的这个流程:
1: 对所有外层参数按照参数的ASCII从小到大排序
2:对排序后的参数列表去除为空的参数并用这个进行MD5加密,然后再转成大写
3:用第一步排序后的参数列表拼上第二步生成的签名作为一个json字符串
因为我们后台要求的使用RequestBody的形式请求,输入的是json字符串,所以我写了一个工具类先是把这个json字符串转成Map,然后再排序。方法如下:
/**
* 将RequestBody转成map并按照key的字母顺序进行排序得出新的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进行排序
*
* @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为空的项,用不为空的项再次转成json字符串然后MD5加密:
这里涉及到一个Map遍历删除的方法,需要注意的是next方法在程序的一次流程中只能被调用一次,否则它会去找下一项可能导致判断的有的判断不准确甚至如果下一项为空可能会导致报错:NoSuchElementException,所以我们可以这样使用:
Map.Entry<String,Object> entry = iterator.next();
Object value = entry.getValue();
/**
* 带签名的传输参数
*
* @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 对排序后的参数进行MD5加密生成大写字母的签名
*/
public static String getSign(Map<String, Object> newMap) {
String paramsStr = GsonUtils.ObjectToJsonStr(newMap);
return MD5Utils.getMd5Value(paramsStr).toUpperCase();
}
这里一开始使用的是Gson的fromJson方法把json转成map,可是出现了一个解析的问题,这个方法会把int的比如1转成double的1.0,这样后面MD5后就不一样了,所以就找度娘怎么解决,找了半天试了有四五种,终于让我找到一个可以的方案:
解析这块儿所有的代码如下:
public static Map<String, Object> jsonToMap(String json) {
Map<String,Object> map = fromJson(json,new TypeToken<Map<String, Object>>(){});
return map;
}
/**
* json字符串转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类,并单独处理Map类型的反序列化。注意:目前只绑定了Map类型,其子类(HashMap)的处理没有变化。具体代码见本文最后或GitHub(发布后会给出地址)。
return gson.fromJson(json, typeToken.getType());
}
//重点是这里
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:
/**
* 改写数字的处理逻辑,将数字值分为整型与浮点型。
*/
double dbNum = in.nextDouble();
// 数字超过long的最大值,返回浮点类型
if (dbNum > Long.MAX_VALUE) {
return dbNum;
}
// 判断数字是否为整数值
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 {
// 序列化无需实现
}
}
另外还需要注意的一点是为了保证我们客户端的签名和后台的签名一致,务必要保证MD5的编码格式跟我们的保持一致,我这里的MD5方法提供出来:
/**
* 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 : 请求参数签名工具类
-
@version: 1.1.0
*/
public class SignUtil {/**
- 带签名的传输参数
- @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 对排序后的参数进行MD5加密生成大写字母的签名
*/
public static String getSign(Map<String, Object> newMap) {
String paramsStr = GsonUtils.ObjectToJsonStr(newMap);
return MD5Utils.getMd5Value(paramsStr).toUpperCase();
}
/**
- 将RequestBody转成map并按照key的字母顺序进行排序得出新的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进行排序
- @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); }
}
}
网友评论