1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
@RequiredArgsConstructor
@ControllerAdvice(assignableTypes = {OpenApiController.class})
@CommonsLog
public class RequestDecryptAdvice extends RequestBodyAdviceAdapter {
// 请求时间戳检验的时间限制
private static Integer TIME_LIMIT = 5;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> clazz = (Class) targetType;
return Encryptable.class.isAssignableFrom(clazz);
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
Class<?> clazz = (Class) targetType;
if (Encryptable.class.isAssignableFrom(clazz)) {
log.info("校验OpenApi请求");
ByteArrayInputStream inputStream = convert(inputMessage.getBody());
log.info("OpenApi请求校验成功");
return new MappingJacksonInputMessage(inputStream, inputMessage.getHeaders());
} else {
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
/**
* 校验签名
*
* @param body 请求体
* @return 转换后的请求参数体
*/
private ByteArrayInputStream convert(InputStream body) throws IOException {
String content = StreamUtils.copyToString(body, StandardCharsets.UTF_8);
EncryptModel encryptModel = JsonUtil.readValue(content, EncryptModel.class);
log.info("OpenApi请求校验, 校验参数: " + JsonUtil.toJson(encryptModel));
checkEncryptModel(encryptModel);
Long timestamp = encryptModel.getTimestamp();
log.info("OpenApi请求校验, 校验时间戳: " + timestamp);
checkTimestamp(timestamp);
String dataFrom64 = encryptModel.getData();
byte[] data = Base64.decodeBase64(dataFrom64);
encryptModel.setData(new String(data));
String rawSign = getRowSign(encryptModel);
log.info("构建参数, 待签名参数:" + rawSign);
String sign = EncryptUtils.hmacSha1(rawSign, HMAC_KEY);
log.info("OpenApi请求校验, 校验签名: " + sign);
if (StringKit.ne(sign, encryptModel.getSignature())) {
throw new OpenApiException("签名校验失败");
}
return new ByteArrayInputStream(data);
}
/**
* 拼接参数,生成待签名的字符串
* @param encryptModel 请求参数
* @return 待签名字符串
*/
private String getRowSign(EncryptModel encryptModel) {
String data = encryptModel.getData();
JsonNode dataNode = JsonUtil.readValue(data, JsonNode.class);
List<String> paramList = new ArrayList<>();
paramList.add("timestamp=" + EncryptUtils.urlEncode(encryptModel.getTimestamp().toString()));
dataNode.fieldNames().forEachRemaining(key -> {
String value = dataNode.get(key).asText(null);
if (StringUtils.isBlank(value)) {
return;
}
String encode = EncryptUtils.urlEncode(value);
paramList.add(key + "=" + encode);
});
paramList.sort(String::compareTo);
return String.join("&", paramList);
}
/**
* 校验加密请求参数
* @param encryptModel 参数
*/
private void checkEncryptModel(EncryptModel encryptModel) {
if (encryptModel == null) {
throw new OpenApiException("参数不能为空");
}
if (encryptModel.getTimestamp() == null) {
throw new OpenApiException("缺少参数: timestamp");
}
if (encryptModel.getData() == null) {
throw new OpenApiException("缺少参数: data");
}
if (encryptModel.getSignature() == null) {
throw new OpenApiException("缺少参数: signature");
}
}
/**
* 校验请求时间戳
* @param timestamp 时间戳
*/
private void checkTimestamp(Long timestamp) {
Instant now = Instant.now().atZone(ZoneId.systemDefault()).toInstant();
Instant time = Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toInstant();
if (now.isBefore(time) || now.plus(TIME_LIMIT, ChronoUnit.MINUTES).isBefore(time)) {
throw new OpenApiException("无权访问");
}
}
}
|