我们Android端的数据请求很容易,通常都是使用Retrofit来实现的。
/**
* 申请添加对方为好友。
*
* @param from 好友请求发起的用户id
* @param to 被发起好友请求的用户id
* @return 好友请求是否送达
*/
@POST("v2/friendRequestSend")
@FormUrlEncoded
suspend fun addFriendRequest(
@Field("from") from: String,
@Field("to") to: String
): ApiResult<Boolean>
这是使用form表单请求的一种方式。这种请求很容易被伪造,从而绕开客户端做一些盗版的app。那么问题来了,怎么优化?
@POST("v3/friendRequestSend")
suspend fun addFriendRequestV3(@Body body: RequestBody): Flow<ApiResult<Boolean>>
在请求数据model类的基类中添加一个转换为RequestBody的方法。
fun toRequestBody() : RequestBody {
return Gson().toJson(this).toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
}
先修改为RequestBody的请求方式,然后我们再对RequestBody做文章。
如何防止伪造数据
我们发出去的RequestBody是有一定玄机的,除了业务相关的字段,还应该添加一些公共字段。这些公共字段包括timestamp、random和signature等。先说签名,签名针对的是大部分字段。将这些字段放入一个按key的字母排序的hashmap,先转为json字符串,再对json字符串做一些加密。这样服务端也对这些字段排序,并按同样的方式进行签名,然后比对客户端传入的数据有没有被修改。如果没有被黑客改过,很好,我们再接着做下一步校验。random是否是按照我们的特定算法随机出来的随机数,并非完全随机。如果不是,则说明不是由客户端签名出来的,直接拒绝请求。说到这里,有些人可能会动脑筋,我抓包,直接按你的数据发送请求不就好了?对不起,平台就是可以为所欲为的。你的签名过期了,我不认!你能把我怎么着?这时timestamp就派上用场了。如果我服务端收到请求的时间减去客户端签名时的时间戳,发现竟然过了整整3秒钟。这时服务端则认为,你是不是穿越过来的?都什么年代了,网速竟然可以如此之慢!
如何防止请求数据泄露
如果你的app对请求的安全级别更高,那么你就要使用RSA非对称加密了。生成一个RSA密钥对,即公钥和私钥。这些业务相关的字段就不要传了,先将其转换为json字符串,然后使用RSA的公钥对其进行加密,再传给服务端。服务端使用相对应的私钥对数据进行解密。注意私钥请妥善保管,不要泄露。
服务端的大致实现
Req req = new Req("1", "3", "2");
List<String> list = new ArrayList<>();
// 签名数据后面加进去
list.add("signature");
// 创建一个HashMap并添加一些键值对
Map<String, Object> hashMap = new HashMap<>();
Field[] fields = req.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (list.contains(field.getName())) {
continue;
}
try {
hashMap.put(field.getName(), field.get(req));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 将HashMap的键按字母顺序排序
List<String> sortedKeys = new ArrayList<>(hashMap.keySet());
Collections.sort(sortedKeys);
// 创建一个新的按键排序的HashMap
SortedMap<String, Object> sortedMap = new TreeMap(hashMap);
// 将排序后的HashMap转为JSON字符串
// 创建ObjectMapper对象
ObjectMapper objectMapper = new ObjectMapper();
// Jackson生成Java对象为JSON字符串
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(sortedMap);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// 签名数据
sortedMap.put("signature", encryptToMD5(encryptToAES(jsonString)));
System.out.println("signature="+encryptToMD5(encryptToAES(jsonString)));
总结
数据安全攻防是一个持续对抗的问题。树大招风,树欲静而风不止。你的平台如果要做大做强,必然要将数据安全放在非常重要的位置,可以说是一个企业的命脉。对数据多做几层加密,养兵千日,用兵一时。一旦有人发出攻击,哪怕复杂度再高一点点,那就对你没有办法。最后祝大家,技术更上一层楼,多多学习新技术,技术的更新换代很快,如果不思进取,则可能在时代的潮流中被淘汰。