为什么要做签名?
想象一个场景:一位许久不见的朋友,突然在微信里面跟你说“朋友,借200应个急”,你会怎么反应?
我想大部分人马上的反应就是:是不是被盗号了?他是本人吗?
实际上这是我们日常生活中常见的通讯行为,系统间调用API和传输数据的过程无异于你和朋友间的微信沟通,所有处于开放环境的数据传输都是可以被截取,甚至被篡改的。因而数据传输存在着极大的危险,所以必须签名加密
。
签名核心解决两个问题:
- 请求是否合法:是否是我规定的那个人
- 请求是否被篡改:是否被第三方劫持并篡改参数
- 防止重复请求(防重放):是否重复请求
如何进行签名
签名算法逻辑
第一步, 设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大
排序(字典序),使用以下格式拼接成字符串stringA
1 | erlang复制代码key1value1key2value2... |
特别注意以下重要规则:
- 参数名ASCII码从小到大排序(字典序);
- 如果参数的值为空不参与签名;
- 参数名区分大小写;
- 传送的sign参数不参与签名;
第二步,在stringA最后拼接上secret密钥得到stringSignTemp字符串
第三步,对stringSignTemp进行MD5加密得到signValue
防重放攻击
以上措施依然不是最严谨的,虽然仿冒者无法轻易模仿签名规则再生成一模一样的签名,可事实上,如果仿冒者监听并截取到了请求片段,然后把签名单独截取出来模仿正式请求方欺骗服务器进行重复请求,这也会造成安全问题,这攻击方式就叫重放攻击(replay 攻击)。
我们可以通过加入 timestamp
+ nonce
两个参数来控制请求有效性,防止重放攻击。
timestamp
请求端:timestamp
由请求方生成,代表请求被发送的时间(需双方共用一套时间计数系统)随请求参数一并发出,并将 timestamp
作为一个参数加入 sign
加密计算。
服务端:平台服务器接到请求后对比当前时间戳,设定不超过60s 即认为该请求正常,否则认为超时并不反馈结果(由于实际传输时间差的存在所以不可能无限缩小超时时间)。
但是这样仍然是仅仅不够的,仿冒者仍然有60秒的时间来模仿请求进行重放攻击。所以更进一步地,可以为sign
加上一个随机码(称之为盐值)这里我们定义为 nonce
。
nonce
请求端:nonce
是由请求方生成的随机数(在规定的时间内保证有充足的随机数产生,即在60s 内产生的随机数重复的概率为0)也作为参数之一加入 sign
签名。
服务端:服务器接受到请求先判定 nonce
是否被请求过(一般会放到redis
中),如果发现 nonce
参数在规定时间是全新的则正常返回结果,反之,则判定是重放攻击。而由于以上2个参数也写入了签名当中,攻击方刻意增加或伪造 timestamp
和 nonce
企图逃过重放判定都会导致签名不通过而失败。
前端生成签名
一般在axios发送请求处统一拦截
1 | javascript复制代码// npm install crypto-js |
如何解决时间差问题
如果客户端时间与服务器时间不一致时(客户端时间比服务端快2分钟
), 如果验签时规定 一分钟内的请求有效,则该签名永远无法通过。
- 第一次打开应用获取本地时间,然后请求接口获取服务器时间。
1 | js复制代码 // 获取服务器时间 |
- 把时间差保存到本地存储
- 请求接口的时候把本地时间和时间差相加。
后端生成签名
1 | java复制代码import org.apache.commons.codec.binary.Hex; |
验证签名
1 | java复制代码 public static void validateSign(Map<String, Object> params) { |
🚀🚀🚀🚀个人开源了一款基于React、TypeScript、Zustand、Ant Design开发的高颜值后台管理系统Slash Admin, 马上就有1000star了,感兴趣的可以了解下🚀🚀🚀🚀
本文转载自: 掘金