如图,是我们模拟的一个从浏览器发送给服务器端的转账请求。久一的ID是 web_resource,正在操作100元的转账。
再如图,因为是通过浏览器 `url` 访问服务,这个时候金额被篡改成了 200,那么服务器接受到了200,直接扣除了200怎么解决?这就是本文要讲解的内容。
防止url被篡改的方式有很多种,本文就讲述最简单的一种,通过 secret 加密验证。
道理很简单,服务器接收到了 price 和 id,如果有办法校验一下他们是否被修改过不就就可以了吗?
那么我们传递的时候增加一个参数,叫做sign,sign是使用用户不可见的一个secret和price、id组合加密获得,然后传递给服务器端。当服务器端接收到请求的时候,获取到price、id,通过同样的secret加密和sign比较如果相同就通过校验,不同则被篡改过。
那么问题来了,如果参数特别多怎么办?
所以通用的做法是,把所有需要防止篡改的参数按照字母正序排序,然后顺序拼接到一起,再和secret组合加密得到 sign。具体的做法可以参照如下。
public static
String
generateSign
(Map<String
,
String> parameters) {
try
{
List<String> names =
new
ArrayList<>()
;
parameters.forEach((k
,
v) -> {
if
(v !=
null
&& !Objects.equals(v
,
""
)
&& !Objects.equals(k
,
"sign"
)) {
names
.add(k)
;
}
})
;
List sortedNames = names.stream().sorted()
.collect(Collectors.toList())
;
StringBuffer sb =
new
StringBuffer()
;
sortedNames.forEach(n ->
sb
.append(String.format(
"%s=%s"
,
n
,
parameters
.get(n))))
;
String sign = md5(sb.toString())
;
return
sign
;
}
catch
(Exception e) {
return
""
;
}
}
private static
String
md5
(String inputString)
throws
NoSuchAlgorithmException
,
UnsupportedEncodingException {
MessageDigest md = MessageDigest.getInstance(
"MD5"
)
;
md.update(inputString.getBytes(
"UTF-8"
))
;
byte
[] digest = md.digest()
;
return
convertByteToHex(digest)
;
}
private static
String
convertByteToHex
(
byte
[] byteData) {
StringBuilder sb =
new
StringBuilder()
;
for
(
int
i =
0
;
i < byteData.
length
;
i++) {
sb.append(Integer.toString((byteData[i] &
0xff
) +
0x100
,
16
)
.substring(
1
))
;
}
return
sb.toString()
;
}
generateSign 就是所有需要加密的参数,包括secret
有的同学担心,那么他万一猜到了我的加密算法怎么办,这个不用担心,你的secret是保持在服务器端的,不会暴漏出去的,所以他知道了算法也不会知道具体加密的内容。
那么问题又来了,如果小明通过抓包工具获取到了URL,他是不是可以无限制的访问这个地址呢?那就出现了“久一”的钱被一百一百的转空了。
那可怎么办?这里涉及到了另一个话题,接口的幂等,我们后面会详细讲解怎么通过幂等控制重复扣款。这里我们要讲解的是怎么控制 URL 失效。
这里又有一个通用的做法,就是再添加一个参数 timestamp。对的,就是当前的时间戳。服务器获取到 timestamp 以后检验一下是否在5分钟以内,如果不是直接返回请求失效就可以了?那么如果timestamp 被篡改了呢?不会的,因为我们按照上面的做法同样对 timestamp 做了加密防止篡改。
最简单的校验接口被篡改的方式,你学会了吗?
原文发布时间为:2018-10-29
本文作者:王久一
本文来自云栖社区合作伙伴“Web项目聚集地”,了解相关信息可以关注“Web项目聚集地”。