Android-接口数据加密

2020-07-23  本文已影响0人  超人TIGA

为了增加项目的安全性,避免被别人通过拦截接口请求和相关参数,从而模拟数据并频繁调用接口,造成服务器压力过大;还有一些重要的接口,例如红包、抽奖、充值兑换等,如果被恶意刷,都会对服务器和项目造成十分严重的影响。所以,对接口请求时加密或者防止被刷接口就很有必要了。

我使用过的加密方式有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对的上的请求,服务器才做出响应,达到了一定的防止刷接口的风险。

上一篇 下一篇

猜你喜欢

热点阅读