一、验签的背景
在网络发展快速的过程中,总是会忽略接口数据安全问题,进行验签则能够在一定程度上能够防刷,数据篡改。
二、什么是加签验签
加签验签,
发送消息方,对消息加签名;
接受消息方,验证签名是否正确。
发送消息方:
1、根据消息内容形成摘要
2、根据摘要形成签名字段
3、发送消息
接受消息方:
1、接受消息
2、根据消息内容形成摘要
3、根据摘要去验证签名是否正确
三、实物电商验签规则
3.1 请求头 Header 增加参数
3.1.1 Expires:时间戳 单位 毫秒
3.1.2 X-Request-Id:(前端生成的随机数<数字加字母的组合>)
3.1.3 Signature:摘要 生成
- signstr: 请求参数ASCALL表排序,再按照该顺序拼接成一个字符串
- X-Request-Id:请求id
- Expires:时间戳
公式:Signature = signstr + requestId + time
注意:
时间校准,空值的key 不参与ASCALL表排序
参考:
可以参考下面的值来测试生成的签名是否正确
- Accept: application/json, text/plain, */*
- Accept-Encoding: gzip, deflate
- Accept-Language: en_US
- Authorization: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiIxNTk5NjM2NTUyOEAxNjMuY29tIiwic2NvcGUiOlsiYWxsIl0sImlkIjozNTU5LCJleHAiOjE2Njg1ODkxMjUsImF1dGhvcml0aWVzIjpbIuWJjeWPsOS8muWRmCJdLCJqdGkiOiIyZDAxMWVhOS02N2RjLTRmZmQtOTAwMS1jYTMwNTU1MzM0NzkiLCJjbGllbnRfaWQiOiJmcm9udC1hcHAifQ.AkKh_rTiIn1tckM-hvbM5o9lKEESd9TFz19W8xKDVcVisgwZ6tUTYcROB3JUdijGq-_7VnEnFjmjY3qXYeOHngo_ctDgw6KQqkDghSHGUDohI2SLXiTuHDZtp6H_f1wnTXYnHWqox7hwiNIWYbckkh4sRw6jkNDQAf6KfxccJ0Y
- Connection: keep-alive
- Cookie: _ga=GA1.1.500562097.1650948294; _ga_TZED0ZY3LF=GS1.1.1667983869.442.1.1667984325.0.0.0
- Expires: 1667984325369
- Host: h5.newsitunemall.com
- Referer: http://h5.newsitunemall.com/mine
- Signature: 1c356d047bcb10de8c1700ab3f3f25ec
- Source: h5
- User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
- version: 1.0.82
- X-Request-Id: acc9d18b85159844ea89e758747ecfa3
3.2 时间校准逻辑
3.2.1 请求被动触发
具体接口:
接口:home/timestamp
方式:GET
返回:
{
"code": 200,
"message": "operation success",
"data": 1668653868836,
"timestamp": 1668653868836,
"success": true
}
3.2.2 首页 主动触发
首页被用户点击的时候,主动请求一下接口,校准时间戳
3.2.3 前端验签规则:
3.2.4 后端解签代码
public void checkSign(String sign, Map<String, String> requestParam,String timestamp, String requestId, URI uri){
if (StrUtil.isBlank(sign) || StrUtil.isBlank(timestamp) || StrUtil.isBlank(requestId)) {
LOGGER.error("illegal request sign:{} timestamp:{} requestId:{}",sign,timestamp,requestId);
throw new ApiException("illegal request,param is null!");
}
long time;
try {
time = Long.parseLong(timestamp);
} catch (Exception e) {
LOGGER.error("illegal request timestamp不合法,timestamp:" + timestamp);
throw new ApiException("timestamp illegal");
}
//验证时间戳是否过期
long currentTime = System.currentTimeMillis();
if (time > currentTime + timeOver || time < currentTime - timeOver) {
LOGGER.error("illegal request timestamp expires now:{} timestamp:{}",currentTime,time);
throw new ApiException("please refresh");
}
if(redisTemplate.hasKey(requestId)){
LOGGER.error("illegal request requestId exist:{}",requestId);
throw new ApiException("illegal request,requestId exist:"+requestId);
}else{
redisTemplate.opsForValue().set(requestId, requestId,timeOver, TimeUnit.MILLISECONDS);
}
String paramAscII = MapUtil.sortJoin(requestParam, "&", "=", true, requestId, timestamp);
String md5 = new Digester(DigestAlgorithm.MD5).digestHex(paramAscII);
if (!md5.equals(sign)) {
LOGGER.error("illegal request,check sign fail! paramAscII:{} sourceSign:{} sysSign:{}",paramAscII,sign,md5);
throw new ApiException("illegal request,check sign fail");
}
}