1.原理

  1. 过滤器,过滤所有请求,利用HttpServletRequestWrapper解决request中流读取一次的处理,方便后续修改请求内容
  2. 自定义注解,通过自定义注解可以标识,指定哪些接口在拦截器中处理数据
  3. 拦截器,拦截带有指定注解的请求,把数据进行加密解密后返回处理
  4. 优势:通过注解形式,不需要改变原接口请求参数,在拦截器里面把加密数据解密为原接口请求参数。同时支持application/x-www-form-urlencoded和application/json 的解密

2.支持范围

2.1.实际可以自己改造适合多种情况处理,已支持以下

1.application/json 加密参数在body
2.application/x-www-form-urlencoded 支持参数在body或者在param

2.2.为什么不用RequestBodyAdvice

1.因为RequestBodyAdvice只支持body内容的数据加解密处理,具有局限性。


3.具体实现代码

3.1. 过滤器(目的读取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
/**
* @Author: bright chen
*/
@WebFilter(value = "/*", filterName = "uriFormatFilter")
public class UriFormatFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {

String uri = httpServletRequest.getRequestURI();
String newUri = uri.replace("//","/");
httpServletRequest = new HttpServletRequestWrapper(httpServletRequest){
@Override
public String getRequestURI() {
return newUri;
}
};
ServletRequest requestWrapper=new RequestWrapper(httpServletRequest);
if(requestWrapper!=null){
filterChain.doFilter(requestWrapper,httpServletResponse);
}else{
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
}

3.2.自定义HttpServletRequestWrapper(重点1,param赋值,body赋值,param pojo读取时赋值)(2024-08-08 优化兼容springboot actuator)

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
* @Author: bright chen
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private String body;
private byte[] bodyBytes;
private Map<String, String[]> params = new HashMap<String, String[]>();

public RequestWrapper(HttpServletRequest request) {
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
String contentType = request.getContentType();
//RequestWrapper继承了HttpServletRequestWrapper,获取request并读取了inputstream把inputsream转为string,而文件上传时,inpustream转为string,就会出现解析出错,文件损坏
if (contentType!=null&& contentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
bodyBytes= StreamUtils.copyToByteArray(inputStream);
inputStream= new ByteArrayInputStream(bodyBytes);
}
if (contentType != null && contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
bodyBytes = StreamUtils.copyToByteArray(request.getInputStream());
body = new String(bodyBytes, StandardCharsets.UTF_8);
params.putAll(parseParameters(body));
}
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {

} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
this.body = stringBuilder.toString();
this.params.putAll(request.getParameterMap());
}

private Map<String, String[]> parseParameters(String body) {
Map<String, String[]> parameterMap = new HashMap<>();
String[] pairs = body.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String name = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8) : pair;
String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8) : "";
parameterMap.computeIfAbsent(name, k -> new String[]{});
String[] existingValues = parameterMap.get(name);
String[] newValues = Arrays.copyOf(existingValues, existingValues.length + 1);
newValues[newValues.length - 1] = value;
parameterMap.put(name, newValues);
}
return parameterMap;
}
@Override
public Map<String, String[]> getParameterMap() {
return params;
}


@Override
public ServletInputStream getInputStream() throws IOException {

ByteArrayInputStream byteArrayInputStream =null;
if(bodyBytes!=null&&bodyBytes.length>0){
byteArrayInputStream = new ByteArrayInputStream(bodyBytes);
}else {
byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
}

ByteArrayInputStream finalByteArrayInputStream = byteArrayInputStream;
ServletInputStream servletInputStream = new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}

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

@Override
public void setReadListener(ReadListener readListener) {
}

@Override
public int read() throws IOException {
return finalByteArrayInputStream.read();
}
};
return servletInputStream;
}


// 重载一个构造方法
public RequestWrapper(HttpServletRequest request, Map<String, Object> extendParams, String body) {
this(request);
if (body != null && body.length() > 0) {
setBody(body);
}
if (extendParams.size() > 0) {
addAllParameters(extendParams);// 这里将扩展参数写入参数表
}
}


@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}

public String getBody() {
return this.body;
}

// 赋值给body字段
public void setBody(String body) {
this.body = body;
}


@Override
public String getParameter(String name) {// 重写getParameter,代表参数从当前类中的map获取
String[] values = params.get(name);
if (values == null || values.length == 0) {
return null;
}
return values[0];
}

public String[] getParameterValues(String name) {// 同上
return params.get(name);
}


/**
* 修改此方法主要是因为当RequestMapper中的参数为pojo类型时,
* 会通过此方法获取所有的请求参数并进行遍历,对pojo属性赋值
*
* @return
*/
@Override
public Enumeration<String> getParameterNames() {// 同上
ArrayList<String> list = list = new ArrayList<>();
for (Map.Entry<String, String[]> entry : params.entrySet()) {
list.add(entry.getKey());
}
return Collections.enumeration(list);
}

public void addAllParameters(Map<String, Object> otherParams) {// 增加多个参数
for (Map.Entry<String, Object> entry : otherParams.entrySet()) {
addParameter(entry.getKey(), entry.getValue());
}
}


public void addParameter(String name, Object value) {// 增加参数
if (value != null) {
if (value instanceof String[]) {
params.put(name, (String[]) value);
} else if (value instanceof String) {
params.put(name, new String[]{(String) value});
} else {
params.put(name, new String[]{String.valueOf(value)});
}
}
}

public byte[] getBodyBytes() {
return bodyBytes;
}

public void setBodyBytes(byte[] bodyBytes) {
this.bodyBytes = bodyBytes;
}
}

3.3.自定义注解 @DecryptApi,后期在controller层带上这个注解则该controller层接口会被过滤器及拦截器进行解密处理。

1
2
3
4
5
6
7
8
9
/**
* 支持params和body的解密
* 支持
* @Author: bright chen
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface DecryptApi {
}

3.4. (核心代码) 拦截器 DecryptInterceptor,对带有@DecryptApi注解的接口数据进行解密处理后重新赋值,注意配置 拦截所有请求

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
* @Author: bright chen
* api 解密拦截器
*/
public class DecryptInterceptor extends HandlerInterceptorAdapter {
private static Logger logger = LogManager.getLogger(DecryptInterceptor.class);
@Autowired
private EncryptProperties encryptProperties;

/**
* Controller之前执行
* preHandle:拦截于请求刚进入时,进行判断,需要boolean返回值,如果返回true将继续执行,如果返回false,将不进行执行。一般用于登录校验
* 1.当preHandle方法返回false时,从当前拦截器往回执行所有拦截器的afterCompletion方法,再退出拦截器链。也就是说,请求不继续往下传了,直接沿着来的链往回跑。
* 2.当preHandle方法全为true时,执行下一个拦截器,直到所有拦截器执行完。再运行被拦截的Controller。然后进入拦截器链,运行所有拦截器的postHandle方法,
* 完后从最后一个拦截器往回执行所有拦截器的afterCompletion方法.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {


if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
// RequireLogin annotation = method.getMethodAnnotation(RequireLogin.class);
if (method.hasMethodAnnotation(DecryptApi.class)) {
// 需要对数据进行加密解密
// 1.对application/json类型
String contentType = request.getContentType();
if (contentType == null && !"GET".equals(request.getMethod())) {
// 请求不通过,返回错误信息给客户端
responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
return false;
}
String requestBody = null;
boolean shouldEncrypt = false;

if ((contentType != null && StringUtils.substringMatch(contentType, 0,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) || "GET".equals(request.getMethod())) {
// 1.application/x-www-form-urlencoded 支持参数在body或者在param
shouldEncrypt = true;
requestBody = convertFormToString(request);
if (requestBody == null || "{}".equals(requestBody)) {
requestBody = URLDecoder.decode(convertInputStreamToString(request.getInputStream()),
"UTF-8");
List<String> uriToList =
Stream.of(requestBody.split("&")).map(elem -> new String(elem)).collect(Collectors.toList());

Map<String, String> uriToListToMap = new HashMap<>();


for (String individualElement : uriToList) {
if (individualElement.split("=")[0] != null && !"".equals(individualElement.split("=")[0])) {
uriToListToMap.put(individualElement.split("=")[0],
individualElement.substring(individualElement.split("=")[0].length() + 1));
}

}
requestBody = JSONObject.toJSONString(uriToListToMap);
}

} else if (StringUtils.substringMatch(contentType, 0, MediaType.APPLICATION_JSON_VALUE)) {
// application/json 支持加密参数在body
shouldEncrypt = true;
requestBody = convertInputStreamToString(request.getInputStream());

}

if (requestBody == null || "{}".equals(requestBody)||!shouldEncrypt) {
return true;
} else {

String result = decodeApi(JSON.parseObject(requestBody, StdRequestApi.class),
encryptProperties.getPrivateKey());
if (result == null) {
// 请求不通过,返回错误信息给客户端
responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
return false;
}
JSONObject jasonObject = JSONObject.parseObject(result);
Map map = (Map) jasonObject;
if (request instanceof RequestWrapper) {
RequestWrapper requestWrapper = (RequestWrapper) request;
requestWrapper.setBody(result);
requestWrapper.addAllParameters(map);
// requestWrapper = new RequestWrapper(request, map, result);
return true;
}
}
} else {
String contentType = request.getContentType();
if (contentType != null && contentType.length() > 0 && StringUtils.substringMatch(contentType, 0,
MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
// 1.application/x-www-form-urlencoded 支持参数在body或者在param
String requestBody = convertFormToString(request);
if (requestBody == null || "{}".equals(requestBody)) {
// 把流数据放进param中,不解密
requestBody = URLDecoder.decode(convertInputStreamToString(request.getInputStream()),
"UTF-8");
List<String> uriToList =
Stream.of(requestBody.split("&")).map(elem -> new String(elem)).collect(Collectors.toList());

Map<String, Object> uriToListToMap = new HashMap<>();

for (String individualElement : uriToList) {
if (individualElement.split("=")[0] != null && !"".equals(individualElement.split("=")[0])) {
uriToListToMap.put(individualElement.split("=")[0],
individualElement.substring(individualElement.split("=")[0].length() + 1));
}
}
if (request instanceof RequestWrapper) {
RequestWrapper requestWrapper = (RequestWrapper) request;
requestWrapper.setBody(requestBody);
requestWrapper.addAllParameters(uriToListToMap);
return true;
}
}
}
}

return true;
}
return true;

} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage() + "异常地址:" + request.getServletPath());
responseResult(response, response.getWriter(), TResponse.FAIL("Decrypt failed"));
return false;
}
}


/**
* 返回信息给客户端
*
* @param response
* @param tResponse
*/
private void responseResult(HttpServletResponse response, TResponse tResponse) throws IOException {
response.setContentType(HttpConstant.CONTENT_TYPE_JSON);
String json = JSONObject.toJSONString(tResponse);
PrintWriter out = response.getWriter();
out.print(json);
out.flush();
out.close();
}

private String convertFormToString(HttpServletRequest request) {
Map<String, String> result = new HashMap<>(8);
Enumeration<String> parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String name = parameterNames.nextElement();
result.put(name, request.getParameter(name));
}
try {
return JSON.toJSONString(result);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}

private String convertInputStreamToString(InputStream inputStream) throws IOException {
return StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
}


public String decodeApi(StdRequestApi stdRequestApi, String apiPrivateKey) {
try {
// 1.rsa解密
// 2.AES验签
// 3.AES解密
return deData;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

/**
* 返回信息给客户端
*
* @param response
* @param out
* @param tResponse
*/
private void responseResult(HttpServletResponse response, PrintWriter out, TResponse tResponse) {
response.setContentType(HttpConstant.CONTENT_TYPE_JSON);
String json = JSONObject.toJSONString(tResponse);
out.print(json);
out.flush();
out.close();
}


}

3.5.公钥私钥配置读取

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
/**
* @Author: bright chen
*/
@Component
@ConfigurationProperties(prefix = "api")
public class EncryptProperties {
private String privateKey;
private String publicKey;

public String getPrivateKey() {
return privateKey;
}

public void setPrivateKey(String privateKey) {
this.privateKey = privateKey;
}

public String getPublicKey() {
return publicKey;
}

public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}
}

PS:本文编写于公司产品被黑客入侵后的处理方案之一,具体加密解密逻辑不在本文中讲述