签名工具类

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

import cn.hutool.crypto.SecureUtil;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
* @author shuzhuo
*/
public class GenSignUtil {


/**
* 判断签名是否正确,必须包含sign字段,否则返回false。
*
* @param data Map类型数据
* @param secret API密钥
* @return 签名是否正确
* @throws Exception
*/
public static boolean isSignValid(Map<String, String> data, String secret, String salt) {
return genSign(data, salt).equals(secret);
}

public static String genSign(final Map<String, String> data, String salt) {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < keyArray.length; i++) {
String k = keyArray[i];
String dataValue = data.get(k).trim();
if (i < keyArray.length - 1) {
if (dataValue.length() > 0) {
sb.append(k).append("=").append(dataValue).append("&");
}
continue;
}
if (dataValue.length() > 0) {
sb.append(k).append("=").append(dataValue);
}

}

return SecureUtil.md5(sb.append(salt).toString());

}

public static void main(String[] args) {
Map<String, String> data = new HashMap<>();
data.put("appId", "fb3723f17ca99c2bf0c769efba3a5c15");
data.put("appSecret", "a11f78cccadedb9df445a5411b305ae1");
String secret = "fb3723f17ca99c2bf0c769efba3a5c15";
String salt="fb3723f17ca99c2bf0c769efba3a5c15";
System.out.println("sign: "+genSign(data,salt));
System.out.println("校验:" + isSignValid(data, secret,salt));

long timestamp=1672506061000L;
LocalDateTime parse = timestamToDatetime(timestamp);
System.out.println(parse);
System.out.println(parse.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
System.out.println(parse.isBefore(LocalDateTime.now()));

}

/**
* timestamp 转 LocalDateTime
* @param timestamp
* @return
*/
public static LocalDateTime timestamToDatetime(long timestamp){
Instant instant = Instant.ofEpochMilli(timestamp);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}

}

SignAuthInterceptor

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
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
* @author shuzhuo
*/
@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {
private static final String OAUTH2_GET_TOKEN = "/oauth2/getToken";

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

String requestURI = request.getRequestURI();
if (!OAUTH2_GET_TOKEN.equals(requestURI)) {
return true;
}
Map<String, String[]> map = request.getParameterMap();
// 从数组中取出参数放入Map中
Map<String, String> param = new ConcurrentHashMap<>(10);
for (Map.Entry<String, String[]> entry : map.entrySet()) {
String key = entry.getKey();
String[] values = entry.getValue();
for (int i = 0; i < values.length; i++) {
String value = values[i];
param.put(key, value);
}
}

// 获取请求参数timestamp 时间戳,
String timestamp = request.getHeader("timestamp");
if (StringUtils.isBlank(timestamp)) {
log.info("timestamp不能为空...........");
HttpResponseUtil.writer(response, Result.error("timestamp错误"));
return false;
}

Long timestampLong = Long.valueOf(timestamp);
LocalDateTime localDateTime = timestampToDatetime(timestampLong);

/**
* 防止 设置timestamp 为 未来很大的时间,如未来一年之后的时间
*/
if (localDateTime.isAfter(LocalDateTime.now())) {
log.info("timestamp 错误 不能在当前时间之后...........");
HttpResponseUtil.writer(response, Result.error("timestamp错误"));
return false;
}

/** 防止过期时间的提交
* 从前端传递的timestamp 与服务器端当前系统时间之差大于120s,则此次请求的timestamp无效
* 留出短时间考虑网络问题提交速度慢,若时间过长中间时间足以挟持篡改参数,所以折中考虑了120秒
*/
Long time = System.currentTimeMillis();
if (Math.abs(timestampLong - time) > 120000) {
log.info("timestamp失效...........");
HttpResponseUtil.writer(response, Result.error("请求失效"));
return false;
}

String sign = request.getHeader("sign");
if (StringUtils.isBlank(sign)) {
HttpResponseUtil.writer(response, Result.error("sign 错误"));
return false;
}

String appId = request.getParameter("appId");
if (StringUtils.isBlank(appId)) {
HttpResponseUtil.writer(response, Result.error("appId 错误"));
return false;
}

param.put("timestamp", timestamp);
// 通过后台MD5重新签名校验与前端签名sign值比对,确认当前请求数据是否被篡改
boolean result = GenSignUtil.isSignValid(param, sign, appId);
if (!result) {
log.debug("sign签名校验失败...........");
HttpResponseUtil.writer(response, Result.error("sign签名校验失败"));
return false;
}
log.info("签名校验通过,放行...........");
return true;
}

/**
* 时间戳转日期
*
* @param timestamp
* @return
*/
public LocalDateTime timestampToDatetime(long timestamp) {
Instant instant = Instant.ofEpochMilli(timestamp);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

配置增加 addInterceptors

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
import com.stark37125.core.framework.common.interceptor.IpLimitInterceptor;
import com.stark37125.core.framework.common.interceptor.SignAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
* Web MVC配置
*
* @author shuzhuo
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {

@Autowired
private IpLimitInterceptor ipLimitInterceptor;

@Bean
public SignAuthInterceptor signAuthInterceptor() {
return new SignAuthInterceptor();
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(ipLimitInterceptor).addPathPatterns("/**").order(1);
registry.addInterceptor(signAuthInterceptor()).addPathPatterns("/**").order(2);
}
}

测试

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
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.stark37125.core.framework.common.utils.GenSignUtil;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;

@SpringBootTest
public class FastTest {

@Test
public void test(){

String baseUrl="http://127.0.0.1:8080";
String interfaceString = "/oauth2/getToken";

HttpRequest httpRequest = HttpRequest
.get(baseUrl.concat(interfaceString));

String appId = "fb3723f17ca99c2bf0c769efba3a5c15";

// 请求参数
Map<String, String> params = new HashMap<>();
params.put("appId",appId);
params.put("appSecret","a11f78cccadedb9df445a5411b305ae1");
for (Map.Entry<String, String> entry : params.entrySet()) {
httpRequest.form(entry.getKey(),entry.getValue());
}

// 获取毫秒数
long timestamp = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli();
Map<String, String> data = new HashMap<>(params);
data.put("timestamp",String.valueOf(timestamp));
String sign = GenSignUtil.genSign(data, appId);
System.out.println("sign: "+ sign);

// 请求头
httpRequest.header("sign",sign);
httpRequest.header("timestamp",String.valueOf(timestamp));

HttpResponse execute = httpRequest.execute();
String body = execute.body();
System.out.print("body:");
System.out.println(body);


}


}