为了增加项目的安全性,避免被别人通过拦截接口请求和相关参数,从而模拟数据并频繁调用接口,造成服务器压力过大;还有一些重要的接口,例如红包、抽奖、充值兑换等,如果被恶意刷,都会对服务器和项目造成十分严重的影响。所以,对接口请求时加密或者防止被刷接口就很有必要了。
我使用过的加密方式有2种,一种是使用ProtocolBuffers来加密;另一种就是对接口参数进行MD5加密,生成一个token给后台验证,验证通过的才是正常调用。
这次主要讲的是后一种,使用MD5对接口的数据进行加密并生成token。
我使用的网络请求框架是OKhttp+retrofit,所以统一在request里增加interceptor进行拦截处理就好。
1、定义interceptor
class RequestEncryptInterceptor : Interceptor {
private val UTF8 = Charset.forName("UTF-8")
override fun intercept(chain: Interceptor.Chain): Response {
var token: String? = initTokenByMD5(chain.request(), ApiConstants.UUID)
if (!TextUtils.isEmpty(token)) {
builder.addHeader("token", token)
}
return chain.proceed(builder.build())
}
}
上面代码中的ApiConstants.UUID是一个服务器返回的值,在APP一启动的时候,根据手机的唯一码进行请求,服务器返回后保存到手机本地。initTokenByMD5方法就是主要的加密token方法。
private fun initTokenByMD5(request: Request, uuid: String): String {
val uri = Uri.parse(request.url().toString())
var path = uri.path
val map = HashMap<String, Any>()
if (request.body() != null) {
try {
val buffer = Buffer()
request.body()!!.writeTo(buffer)
var charset: Charset? = UTF8
val contentType = request.body()!!.contentType()
if (contentType != null) {
charset = contentType.charset(UTF8)
}
if (isPlaintext(buffer)) {
map.putAll(JsonParseUtil.jsonFormatter(buffer.readString(charset!!)))
}
} catch (e: Exception) {
KLog.e(e)
}
} else {
val names = uri.queryParameterNames
val iterator = names.iterator()
while (iterator.hasNext()) {
val key = iterator.next()
map[key] = uri.getQueryParameter(key)!!
}
}
return TokenUrlValidate.getUrlValidateToken(path, map, uuid)
}
initTokenByMD5方法,作用主要就是把请求里面的参数取出,填入一个HashMap中,而请求又分get和post,所以需要根据request.body()是否为空来判断是哪种类型。else里就是get请求,处理也相对简单,取出键值对,对应填入map就可以了。
而post请求,则需要对请求体进行处理,首先检查内容是否都是相同格式(UTF-8),将多层嵌套的json转化为单层的。
@Throws(EOFException::class)
fun Interceptor.isPlaintext(buffer: Buffer): Boolean {
try {
val prefix = Buffer()
val byteCount = if (buffer.size() < 64) buffer.size() else 64
buffer.copyTo(prefix, 0, byteCount)
for (i in 0..15) {
if (prefix.exhausted()) {
break
}
val codePoint = prefix.readUtf8CodePoint()
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false
}
}
return true
} catch (e: EOFException) {
return false // Truncated UTF-8 sequence.
}
}
/**
* 多层嵌套json数据转换为单层,同时规格化
**/
public class JsonParseUtil {
/**
* 把拍平后的json进行格式化处理,输出标准的json格式
*
* @return
*/
public static Map<String, String> jsonFormatter(String uglyJSONString) {
Map<String, String> map = new HashMap<>();
parseJson2Map(map, uglyJSONString, null);
List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(map.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
return (o1.getKey()).compareTo(o2.getKey());
}
});
return map;
}
public static void parseJson2Map(Map<String, String> map, JsonObject jsonObject, String parentKey) {
for (Map.Entry<String, JsonElement> object : jsonObject.entrySet()) {
String key = object.getKey();
JsonElement value = object.getValue();
String fullkey = (null == parentKey || parentKey.trim().equals("")) ? key : parentKey.trim() + "." + key;
//判断对象的类型,如果是空类型则安装空类型处理
if (value.isJsonNull()) {
map.put(fullkey, null);
continue;
//如果是JsonObject对象则递归处理
} else if (value.isJsonObject()) {
parseJson2Map(map, value.getAsJsonObject(), fullkey);
//如果是JsonArray数组则迭代,然后进行递归
} else if (value.isJsonArray()) {
JsonArray jsonArray = value.getAsJsonArray();
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
JsonElement jsonElement1 = iterator.next();
if (jsonElement1.isJsonPrimitive()) {
// parseJson2Map(map, jsonElement1.getAsJsonArray(), fullkey);
getJsonPrimitive(map, jsonElement1, fullkey);
continue;
}
parseJson2Map(map, jsonElement1.getAsJsonObject(), fullkey);
}
continue;
// 如果是JsonPrimitive对象则获取当中的值,则还需要再次进行判断一下
} else if (value.isJsonPrimitive()) {
getJsonPrimitive(map, value, fullkey);
}
}
}
private static void getJsonPrimitive(Map map, JsonElement value, String fullkey) {
try {
JsonElement element = new JsonParser().parse(value.getAsString());
if (element.isJsonNull()) {
map.put(fullkey, value.getAsString());
} else if (element.isJsonObject()) {
parseJson2Map(map, element.getAsJsonObject(), fullkey);
} else if (element.isJsonPrimitive()) {
JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive();
if (jsonPrimitive.isNumber()) {
map.put(fullkey, jsonPrimitive.getAsNumber());
} else {
map.put(fullkey, jsonPrimitive.getAsString());
}
} else if (element.isJsonArray()) {
JsonArray jsonArray = element.getAsJsonArray();
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
parseJson2Map(map, iterator.next().getAsJsonObject(), fullkey);
}
}
} catch (Exception e) {
Object val = map.get(fullkey);
if (val != null) {
map.put(fullkey, val + value.getAsString());
} else {
map.put(fullkey, value.getAsString());
}
}
}
/**
* 使用Gson拍平json字符串,即当有多层json嵌套时,可以把多层的json拍平为一层
*
* @param map
* @param json
* @param parentKey
*/
public static void parseJson2Map(Map map, String json, String parentKey) {
JsonElement jsonElement = new JsonParser().parse(json);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
parseJson2Map(map, jsonObject, parentKey);
//传入的还是一个json数组
} else if (jsonElement.isJsonArray()) {
JsonArray jsonArray = jsonElement.getAsJsonArray();
Iterator<JsonElement> iterator = jsonArray.iterator();
while (iterator.hasNext()) {
JsonElement next = iterator.next();
if (next.isJsonPrimitive()) {
Object val = map.get("array");
if (val != null) {
map.put("array",val+ next.getAsString());
}else {
map.put("array", next.getAsString());
}
continue;
}
parseJson2Map(map, next.getAsJsonObject(), parentKey);
}
} else if (jsonElement.isJsonPrimitive()) {
KLog.e("jsonElement.isJsonPrimitive() please check the json format!");
} else if (jsonElement.isJsonNull()) {
KLog.e("jsonElement.isJsonNull() please check the json format!");
}
}
}
到这里为止,就已经算是把加密生产token前的工作做好了,分别得到了请求url的路径,请求参数封装后的map,还有uuid。接下来就可以进行加密生成token:
public static String getUrlValidateToken(String path, Map<String, Object> mapParam, String uuid) {
String token = path + formatUrlMap(mapParam, false, false) + "&key=" + TaUtils.getAbc() + "&uuid=" + uuid + "&timeStamp=" + ApiConstants.getComputeServerTime() / TotpUtil.STEP;
if (!TnaotApplication.Companion.instance().isRelease()) {
KLog.v("getUrlValidateToken", token);
}
return Md5Util.toMD5(token).toUpperCase();
}
/**
* 方法用途: 对所有传入参数按照字段名的Unicode码从小到大排序(字典序),并且生成url参数串<br>
* 实现步骤: <br>
*
* @param paraMap 要排序的Map对象
* @param urlEncode 是否需要URLENCODE
* @param keyToLower 是否需要将Key转换为全小写
* true:key转化成小写,false:不转化
* @return
*/
private static String formatUrlMap(Map<String, Object> paraMap, boolean urlEncode, boolean keyToLower) {
String buff = "";
Map<String, Object> tmpMap = paraMap;
try {
List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(tmpMap.entrySet());
// 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
@Override
public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
return (o1.getKey()).compareTo(o2.getKey());
}
});
// 构造URL 键值对的格式
StringBuilder buf = new StringBuilder();
for (Map.Entry<String, Object> item : infoIds) {
if (!TextUtils.isEmpty(item.getKey())) {
String key = item.getKey();
String val = "";
if (item.getValue() != null) {
val = item.getValue().toString();
}
if (urlEncode) {
val = URLEncoder.encode(val, "utf-8");
}
if (keyToLower) {
buf.append(key.toLowerCase() + "=" + val);
} else {
buf.append(key + "=" + val);
}
buf.append("&");
}
}
buff = buf.toString();
if (buff.isEmpty() == false) {
buff = buff.substring(0, buff.length() - 1);
}
} catch (Exception e) {
return "";
}
return buff;
}
public class Md5Util {
private static MessageDigest mDigest = null;
static {
try {
mDigest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
/**
* 对key进行MD5加密,如果无MD5加密算法,则直接使用key对应的hash值。
*/
public static String toMD5(String key) {
//获取MD5算法失败时,直接使用key对应的hash值
if (mDigest == null) {
return String.valueOf(key.hashCode());
} else {
return toMD5(key.getBytes());
}
}
public static String toMD5(byte[] bytes) {
if (mDigest == null) {
return "";
}
String cacheKey;
mDigest.update(bytes);
cacheKey = bytesToHexString(mDigest.digest());
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
完成!这样就能根据接口参数加密生成一个token了,只有token对的上的请求,服务器才做出响应,达到了一定的防止刷接口的风险。
网友评论