最近一个项目需要在请求中效验签名 签名的参数用到了请求的数据
如果请求的数据是 application/x-www-form-urlencoded
的格式那么没问题
但是如果请求的数据是 application/json
则用到了 @RequestBody
中的数据
但是由于 InputStream
只能取一次数据
最后导致 Controller
中取不到数据
解决方案
新建一个
SignHttpServletRequestWrapper
包装请求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
73package com.sixi.enquiry.interceptor;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 19:01.
*/
public class SignHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
/**
* Constructs a request object wrapping the given request.
*
* @param request
* The request to wrap
* @throws IllegalArgumentException
* if the request is null
*/
public SignHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = readBytes(request.getInputStream());
}
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
public int read() throws IOException {
return bais.read();
}
public boolean isFinished() {
return bais.available() == 0;
}
public boolean isReady() {
return false;
}
public void setReadListener(ReadListener arg0) {
}
};
}
private byte[] readBytes(InputStream in) throws IOException {
int buffSize = 1024;
try (BufferedInputStream bis = new BufferedInputStream(in); ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize)) {
byte[] temp = new byte[buffSize];
int size;
while ((size = bis.read(temp)) != -1) {
out.write(temp, 0, size);
}
return out.toByteArray();
}
}
}添加一个
SignFilter
过滤器用来封装请求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
43package com.sixi.enquiry.interceptor;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/20 18:58.
*/
public class SignFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// 只处理POST并且ContentType是JSON的请求 这里根据业务情况自行修改
if (HttpMethod.POST.matches(httpServletRequest.getMethod().toUpperCase()) &&
httpServletRequest.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {
ServletRequest requestWrapper = new SignHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
return;
}
}
chain.doFilter(request, response);
}
public void destroy() {}
}在配置类内新增一个 Bean 注册过滤器
1
2
3
4
5
6
7
8
9
10
public FilterRegistrationBean SignFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SignFilter());
registration.addUrlPatterns("/*");
registration.setName("SignFilter");
registration.setOrder(Integer.MAX_VALUE);
return registration;
}新增一个用到了
getInputStream
的拦截器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.sixi.enquiry.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
/**
* @author 喵♂呜
* @since 2017/5/31
*/
public class SignInterceptor extends HandlerInterceptorAdapter {
private String SignHeader = "Sixi-Signature";
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
return true;
}
}新增控制器 打印数据
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
26package com.sixi.enquiry.controller;
import java.io.IOException;
import java.nio.charset.Charset;
import javax.servlet.http.HttpServletRequest;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created with IntelliJ IDEA
*
* @author 喵♂呜
* Created on 2017/12/21 19:04.
*/
public class TestController {
public String test(HttpServletRequest request) throws IOException {
return StreamUtils.copyToString(request.getInputStream(), Charset.forName("UTF-8"));
}
}使用CURL测试 可以获得数据
1
2
3
4$ curl -sX POST http://172.30.34.3:1221/test \
> -H 'Content-Type: application/json' \
> -d '{"id":111,"userId":1,"amount":100,"products":[{"id":123,"name":"testproduct","price":23}]}'
{"id":111,"userId":1,"amount":100,"products":[{"id":123,"name":"testproduct","price":23}]}