传输签名校验

1.axios请求拦截器判断请求类型

  • 当请求为get请求时拼接请求参数

    src\utils\request.js

1
2
3
4
5
6
7
8
9
10
//初始化data,data为需要进行签名的字符串
let data = config.url
if (config.method === 'get' && config.params) {
data += '?'
for (let key in config.params) {
data += (key + "=" + config.params[key]+ "&")
}
//删除多余的&
data = data.slice(0, -1);
}
  • 当请求为post时拼接请求参数

    src\utils\request.js

1
2
3
4
5
if (config.method === 'post' && (typeof config.data) === 'object') {
let dataString = JSON.stringify(config.data);
console.log("dataString: "+ dataString)
data += ('?' + dataString)
}

2.拼接参数并签名

完整签名串

  • 拼接时间戳

src\utils\request.js

1
2
3
4
let timestamp = Date.now()
//插入请求头
config.headers['timestamp'] = timestamp
data += '$'
  • 拼接盐签名并

    src\utils\encrypto\sm3.js

1
2
3
4
5
6
7
8
9
//SM3传输签名盐,请务必和后端保持一致,也可使用随机盐
const TRANSMIT_SALT = "digest_customized_salt"
const SEPARATOR = "$"
//签名算法
function digestSM3 (originalData) {
originalData = (TRANSMIT_SALT + SEPARATOR + originalData)
console.log("originalData: " + originalData)
return SM3.digest(originalData, 'utf8', 'hex')
}
  • 签名生成Sign放入请求头,放行

src\utils\request.js

1
config.headers['sign'] =  digestSM3(data + timestamp)

image-20231230132221145

3.后端获取请Sign和Timestamp

  • 在请求头中获取参数信息

    com/gaomu/filter/SignatureHeaderFilter.java

1
2
String timestamp = request.getHeader("Timestamp");
String sign = request.getHeader("Sign");
  • 判断是否存在,并判断请求是否超时

    com/gaomu/filter/SignatureHeaderFilter.java

1
2
3
4
5
6
7
long timestampThreshold = System.currentTimeMillis();
if (sign == null || timestamp == null) {
throw new ServletException("签名不存在");
//请求体时间戳需在超时时间范围内,且不能是未来时间
} else if (Long.parseLong(timestamp) < (timestampThreshold - TIMEOUT * 1000) || Long.parseLong(timestamp) > timestampThreshold){
throw new ServletException("请求超时");
}

4.判断请求类型

  • 判断请求类型并拼接签名字符串

    com/gaomu/filter/SignatureHeaderFilter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if ("GET".equalsIgnoreCase(request.getMethod()) && Objects.nonNull(request.getParameterMap())) {
data.append("?");
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
String name = entry.getKey();
String[] values = entry.getValue();
data.append(name).append('=').append(values[0]).append('&');
}
data.deleteCharAt(data.length() - 1);

} else if ("POST".equalsIgnoreCase(request.getMethod()) && Objects.nonNull(request.getReader())){
//需要多次调用Reader,请使用包装类 HttpServletRequestWrapper
StringBuilder sb = new StringBuilder();
String line;
try (BufferedReader reader = request.getReader()) {
while ((line = reader.readLine()) != null) {
sb.append(line);
}
}
data.append('?').append(sb);
}

5.签名判断

  • 将拼接好的参数进行签名生成enSign与前端传的Sign做判断,相同则通过放行,不同抛出异常

    com/gaomu/filter/SignatureHeaderFilter.java

1
2
3
4
5
6
7
if (SM3Util.checkSign(data.toString(), sign,  timestamp)){
System.out.println("签名校验通过-OK");
}
else {
throw new ServletException("签名校验失败");
}
filterChain.doFilter(request, response);

5.1.签名逻辑

image-20231230132641025

  • 签名逻辑sm3(salt+$+uri+?+params+$+timestamp)

    com/gaomu/utils/crypto/SM3Util.java

1
2
3
4
5
6
7
public static boolean checkSign(String data, String sign, String timestamp){
data += SEPARATOR + timestamp;
SM3 sm3 = SmUtil.sm3WithSalt((TRANSMIT_SALT + SEPARATOR) .getBytes(StandardCharsets.UTF_8));
byte[] hash = sm3.digest(data.getBytes());
String enSign = HexUtil.encodeHexStr(hash);
return enSign.equals (sign);
}