Http参数验签(基于过滤器)

实现

过滤器

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
/**
* 参数验签过滤器
*/
@Slf4j
public class SignFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("SignFilter init.");
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("SignFilter doFilter.");

HttpServletRequest request = (HttpServletRequest) servletRequest;

RequestWrapper requestWrapper = new RequestWrapper(request);

// TODO 这里获取了所有前端传递的参数, 可能未必是Controller配置的参数
SortedMap<String, String> sortedMap = RequestUtils.getAllParams(requestWrapper);
if (SignUtils.verifySign(sortedMap)) {
log.info("验签通过, path: [{}], params: [{}]", request.getServletPath(), sortedMap.toString());
filterChain.doFilter(requestWrapper, servletResponse);
} else {
log.error("验签失败, path: [{}], params: [{}]", request.getServletPath(), sortedMap.toString());
}

}

@Override
public void destroy() {
log.info("SignFilter destroy.");
}
}

Request重放

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
public class RequestWrapper extends HttpServletRequestWrapper {

private byte[] buffer;

public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte buff[] = new byte[1024];
int read;
while ((read = is.read(buff)) > 0) {
baos.write(buff, 0, read);
}
this.buffer = baos.toByteArray();
}

@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
return new ServletInputStream() {

@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}

@Override
public int read() {
return bais.read();
}
};
}

// 对外提供读取流的方法
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}

过滤器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configuration
public class FilterConfig {

@Bean
public FilterRegistrationBean filterRegistration() {

FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SignFilter());
registration.addUrlPatterns("/*");
registration.setName("SignFilter");
registration.setOrder(1);
return registration;
}
}

提取请求参数

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
/**
* 接口参数提取
*/
public class RequestUtils {


/**
* 将URL请求参数转换成Map
* @author show
* @param request
*/
public static Map<String, String> getUrlParams(HttpServletRequest request) {

String param = "";
if (Objects.isNull(request.getQueryString())) {
return Collections.EMPTY_MAP;
}
try {
param = URLDecoder.decode(request.getQueryString(), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Map<String, String> result = new HashMap<>(16);
String[] params = param.split("&");
for (String s : params) {
int index = s.indexOf("=");
result.put(s.substring(0, index), s.substring(index + 1));
}
return result;
}

/**
* 获取body中参数
* @param request
* @return
* @throws IOException
*/
public static Map<String, String> getBodyParams (HttpServletRequest request) throws IOException {
String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
//转化成json对象
Map<String, String> map = new Gson().fromJson(body, new TypeToken<HashMap<String, String>>() {}.getType());
return map;
}


/**
* 获取url参数和body参数
* @param request
* @return
* @throws IOException
*/
public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {

SortedMap<String, String> sortedParams = new TreeMap<>();

// 获取url参数
Map<String, String> urlParams = getUrlParams(request);
for (Map.Entry entry : urlParams.entrySet()) {
sortedParams.put((String) entry.getKey(), (String) entry.getValue());
}

// 获取body参数
if (!HttpMethod.GET.name().equals(request.getMethod())) {
Map<String, String> bodyParams = getBodyParams(request);
if (null != bodyParams) {
for (Map.Entry entry : bodyParams.entrySet()) {
sortedParams.put((String) entry.getKey(), (String) entry.getValue());
}
}
}
return sortedParams;
}
}

验签工具

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
/**
* 验签工具类
*/
@Slf4j
public class SignUtils {

// 该参数不传递, 作为加密盐计算摘要
public static final String appKey = "wx3f2f5354f615c637";

/**
* 获取参数签名
* @param sortedMap
* @return
*/
public static String getParamsSign(Map<String, String> sortedMap) {

//要先去掉 Url 里的 Sign
sortedMap.remove("sign");
sortedMap.put("appKey", appKey);
String paramsJsonStr = GsonUtils.toJson(sortedMap);

String md5 = DigestUtils.md5DigestAsHex(paramsJsonStr.getBytes()).toUpperCase();
log.info("加签参数: {}, MD5: {}", paramsJsonStr, md5);

return md5;
}

/**
* 参数验签
* @param sortedMap 已排序参数
* @return 验证签名结果
*/
public static boolean verifySign(Map<String, String> sortedMap) {

// 校验请求是否过期, 防止重放攻击
String inTimeStamp = sortedMap.getOrDefault("ts", "0");
LocalDateTime inTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(inTimeStamp)), ZoneOffset.ofHours(8));
Duration duration = Duration.between(inTime, LocalDateTime.now());
long seconds = duration.get(SECONDS);
System.out.println("seconds: " + seconds);
if (seconds > 60) {
System.out.println("请求超时");
return Boolean.FALSE;
}

String urlSign = Objects.nonNull(sortedMap.get("sign"))?sortedMap.get("sign"):null;
log.info("传递签名: [{}]", urlSign);

if (StringUtils.isBlank(urlSign)) {
return false;
}

//把参数加密
String paramsSign = getParamsSign(sortedMap);
log.info("计算签名: [{}]", paramsSign);

return StringUtils.isNotBlank(paramsSign) && urlSign.equals(paramsSign);
}
}

最后

本文到此结束,感谢阅读。如果您觉得不错,请关注公众号【当我遇上你】,您的支持是我写作的最大动力。

参考