SpringBoot 接口内容加密方案(RSA+AES+HMAC校验)认知

99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式

写在前面

  • 工作中遇到,简单整理
  • 博文内容涉及 Web接口内容 类似 https 的加密和防篡改校验
  • 以及具体Java Springboot 项目中如何编码。
  • 理解不足小伙伴帮忙指正 :),生活加油

99%的焦虑都来自于虚度时间和没有好好做事,所以唯一的解决办法就是行动起来,认真做完事情,战胜焦虑,战胜那些心里空荡荡的时刻,而不是选择逃避。不要站在原地想象困难,行动永远是改变现状的最佳方式

持续分享技术干货,感兴趣小伙伴可以关注下 ^_^


在讲这部分内容之前,先看几个问题:


Q: 有了 https 为什么还需要接口 RSA+AES 加密+HMAC 校验

A:https通信加密,而 接口的 RSA+AES 加密+ HMAC 校验 属于内容加密,HTTPS 加密的是传输过程中的数据,确保数据在客户端和服务器之间传输时不会被窃听或篡改。而接口加密是对接口内容的加密,即报文实体加密


Q:用了 https 做通信加密,为什么本地抓包或者说浏览器还是可以看到报文内容 ?

A:浏览器是客户端,看的时候已经发生的解密,抓包工具利用根证书伪造来解密报文。


Q:那么为什么解密不发生在代码层面,而且在客户端就解密了? 这样就可以避免抓包工具解密了

A:换一种角度考虑,在客户端和服务端属于同一级的高信任区域,因为被信任所以可以看到报文数据,而客户端和服务端之间的链路属于低信任区域,所以加密。既解密和加密是对同一信任度的区域而言。加密和解密发生在由低信任度到高信任度之间,是对称的,在服务端高信任区域加密数据到链路的低信任区域,所以同样需要在链路的低信任区域到客户端的高信任区域解密。所以不在代码层面解密,因为加密发生着服务器端的同级信任区。


Q: 如果希望在代码层面解密,应该如何处理,即为什么需要做内容加密?

A: 通过 https 实现了通信加密,但是对于客户端本地来讲,还是可以利用浏览器或者抓包工具获取实际的报文数据,为了避免敏感数据的泄露,把解密控制在代码层面,我们就需要在 客户端和服务端的信任区域上面在加高一级别信任度的信任区域,这个区域就是代码级别的信任区,之前的 https 对全部报文做了加密,现在我们只对交互的报文实体做加密,这也就是内容加密,所以内容加密是为了数据到达客户端(如浏览器)后会被解密出实际响应报文响应实体仍然处于加密状态,防止通过浏览器或者抓包工具直接获取数据。


通过上面的问题,我们可以对内容加密有一定的认知。下面我们看一下如何对内容加密

接口内容加密方案简单介绍

先来简单看下 https 的加密原理:

https 实际上是 http + SSL/TSL(加密认证,防篡改) = https 是在 HTTP 协议的基础上,通过 SSL/TLS 协议对数据进行加密和认证,确保通信的安全性和完整性。

SSL/TLS 的核心功能 SSL/TLS 提供了以下核心功能:
1. 加密:对传输的数据进行加密,防止窃听。
2. 认证:验证服务器的身份,防止中间人攻击。
3. 完整性:确保数据在传输过程中未被篡改。

HTTPS 的工作流程: HTTPS 的加密原理主要依赖于 SSL/TLS 协议,其工作流程如下:

  1. 建立 TCP 连接: 客户端与服务器通过 TCP 三次握手建立连接。
  2. TLS 握手:
    • 客户端 Hello:客户端发送支持的 TLS 版本、加密套件列表和一个随机数。
    • 服务器 Hello:服务器选择 TLS 版本、加密套件,并返回自己的随机数和证书(包含公钥)。
    • 证书验证:客户端验证服务器证书的有效性(是否由可信 CA 签发,是否过期等),防止中间人攻击
    • 密钥交换:客户端生成一个预主密钥(Pre-Master Secret),用服务器的公钥非对称加密后发送给服务器。
    • 生成会话密钥:客户端和服务器使用预主密钥和随机数生成对称加密密钥(Session Key),用于后续通信。

加密通信

  • 客户端和服务器使用对称加密密钥对 HTTP 数据进行加密和解密。
  • 每次通信都会使用 HMAC 或 AEAD 模式验证数据的完整性。

HTTP 像寄明信片,内容公开,容易被偷看或篡改。HTTPS 像寄加密信件,内容被锁在保险箱里,只有收件人有钥匙打开,确保安全性和完整性。 而我们要做的内容加密是在 HTTPS 的基础上,对明信片上面的内容进行加密处理,收件人用钥匙打开之后,明信片上面是密文,还需要用约定的密码来解密出明文

这里的内容加密也使用上面 https 加密的方案,当然还有其他的方案,不同的是 CA证书的获取和认证,即从获取非对称加密的公钥开始,所有的加解密是发生的代码层级的。

常见的加密方案(RSA + AES + HMAC TLS 1.2)

  • 对称加密(如 AES):加密和解密使用相同的密钥,速度快,但密钥分发不安全,用于加密实际传输的数据,保证高效性。
  • 非对称加密(如 RSA):加密和解密使用不同的密钥,安全性高,但速度慢,用于加密 AES 密钥,解决密钥分发问题
  • 消息认证码(如 HMAC):用于验证数据的完整性和真实性,生成签名。

对于一个完整的接口内容加密流程

客户端请求,加密报文过程:

密钥生成,类似 SSL

1
2
3
4
1. 获取非对称的公钥: MIIBIjAN................kqUXgQntOo3HOuzW9pqwIDAQAB
2. 生成使用的对称的密钥: buLZ...CsBsEcd
3. 通过生成的对称密钥对请求报文进行加密:bodySt......14g==
4. 对称密钥通过非对称公钥加密生成传输的密钥: NDI3q..................p86SyQ==

签名的生成,这里使用的是 HMAC ,也可以考虑使用 AEAD

1
2
3
4
5
6
7
8
签名需要的数据
================================== message :
接口类型: POST
接口地址:/hotel/web/threePartyI。。。。。。。。。。。nfoAnonymous
对称密钥通过非对称公钥加密后的密钥(X-Secret 报文头): NDI3qtS...................6SyQ==
随机字符串(X-Nonce 报文头):M2jmJm6Yo9
时间戳(X-Timestamp 报文头):1738897905
请求报文对称加密后的密文哈希值: 6fb2a0229959e25706a7fc50b888f82dbe8688bc

上面的签名数据组合在通过哈希算法和对称密钥做种子生成签名

1
生成的签名(X-Signature 报文头) signature:cdd5bab8e.......73d61753d546b

调用接口:

1
2
3
4
11:11:46.081 [main] INFO ......- crm url:https:.....nymous method: POST body: {"gCertNo":"220882199608126526","gMobile":"18147405370"}
实际的请求报文:bodyStr{"gCertNo":"220882199608126526","gMobile":"18147405370"}
加密后的报文 bodyStroZtulHZvJrRN2sfBs6MvLOCRaLbIh8jgpGHIsE9DFwHBGGp0vuNylE/OOAeo6pYGRP/kkpL8DZPXOMapTbX14g==
.......................................

服务端收到报文,对请求报文做解密处理,同时对响应报文做加密处理

  1. 加载本地的非对称加密的私钥
  2. 判断报文头数据是否存在,同时从报文头获取需要的数据(X-Secret,X-Nonce,X-Timestamp,X-Signature)
  3. 判断时间戳是否符合要求
  4. 通过非对称加密的私钥对对称加密的密钥进行解密,获取对称加密的密钥
  5. HMAC校验,重新生成签名判断是否一致
  6. 解密请求报文给后端接口处理
  7. 接口处理完成返回响应,通过 对称加密的密钥对响应报文进行加密
1
2
3
4
11:11:46.433 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
11:11:46.434 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "application/json;charset=UTF-8"
================ 返回的消息:<200,auePdYEQfYaQgBt3RPMOGLXTxXxO72t8nSk6CQ5aqKXzZ+GWXoxml7pv9+OhwlAE,[vary:"Origin,Access-Control-Request-Method,Access-Control-Request-Headers", x-content-type-options:"nosniff", x-xss-protection:"1; mode=block", strict-transport-security:"max-age=31536000 ; includeSubDomains", x-frame-options:"SAMEORIGIN", content-type:"application/json;charset=UTF-8", content-length:"64", date:"Fri, 07 Feb 2025 03:11:46 GMT", x-envoy-upstream-service-time:"17", server:"istio-envoy"]>

客户端收到响应报文,通过上面请求报文生成的对称加密密钥对响应报文进行解密处理,获取实际的响应报文

1
2
返回的报文:auePdYEQfYaQgBt......Xoxml7pv9+OhwlAE
解密后的报文=>>>{"msg":"......","code":200}

代码实现

下面是一个 SpringBoot 项目的接口加密实现服务端的编码

  1. Spring-Security 添加对应的过滤器,用于处理服务端的请求解密,响应加密:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 加密过滤器
*/
@Autowired
private SecurityFilter securityFilter;

.....................
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
// 添加接口加密过滤器
.addFilterBefore(securityFilter, JwtAuthenticationTokenFilter.class);
// 添加多因素认证
。。。。。。。。
  1. 处理请求报文的解密,响应报文的加密

过滤器方法,这里利用 Java Web 的过滤器链,doFilterInternal 为核心的方法,用于对请求的报文进行解密,然后给传递给其他的过滤器,最后到实际的路由地址,处理完请求的返回响应在对响应报文进行加密给客户端返回数据。

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
/**
* 加密接口的处理(RSA+AES )
* @param request
* @param response
* @param filterChain
*/
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
// 获取请求的路径
String requestUri = request.getRequestURI();
// 只处理特定的路由
if (!requestUri.startsWith(API_PATH_PATTERN)) {
filterChain.doFilter(request, response);
return;
}
// todo 需要注意 `request.getInputStream()` 只能读一次,所以在这里读取
String bodyStr = getRequestBody(request);
// todo ============================= 请求报文的解密处理 ==============================
// 通过 RSA公钥要加的 AES密钥
String secretHeader = request.getHeader("X-Secret");
// 随机数
String nonceHeader = request.getHeader("X-Nonce");
// 时间戳
String timestampHeader = request.getHeader("X-Timestamp");
// 签名
String signatureHeader = request.getHeader("X-Signature");
if (secretHeader == null || nonceHeader == null || timestampHeader == null || signatureHeader == null) {
log.error("请求报文不符合要求!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "signature verification error");
return;
}
// 加载私钥
PrivateKey privateKey = loadPrivateKey(privateKeyStr);
System.out.println("================= 加密的密钥:"+ secretHeader);
// 通过私钥解密客户端用公钥加密的 AES 密钥
String decrypt = RSAFacade.decrypt(CipherTypeEnums.RSA_PKCS1, secretHeader, privateKey);
System.out.println("================= 解密的密钥:"+ decrypt);
// 比较时间戳
long timestamp = Long.parseLong(timestampHeader);
long currTimestamp = Instant.now().getEpochSecond();
if (currTimestamp - timestamp > REQUEST_EXPIRY_TIME) {
log.error("时间戳异常!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "expired request");
return;
}
// HMAC校验 校验签名
String message = buildMessage(request, secretHeader, nonceHeader, timestampHeader,bodyStr);
System.out.println("=========================: 消息"+ message);
String computedSignature = null;
// 生成HMAC校验签名
computedSignature = toHMAC_SHA256( message,decrypt);
System.out.println("========================== 生成的签名:" + computedSignature);
if (!computedSignature.equals(signatureHeader)) {
log.error("签名不一致!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "signature verification error");
return;
}
System.out.println("================加密的报文数据 requestBody: " + bodyStr);
String encryptedBody = null;
// 通过 AES 解密报文
AES aes = AES.getInstance(CipherTypeEnums.AES_CBC_PKCS7, decrypt, decrypt);
encryptedBody = aes.decrypt(bodyStr);
System.out.println("===================== 获取到的报文数据:"+ encryptedBody);
// TODO ======================================= 重新封装请求和响应报文 ==================================
CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper(request, encryptedBody.getBytes());
EncryptingResponseWrapper wrappedResponse = new EncryptingResponseWrapper(response);
// 调用过滤器链处理请求
filterChain.doFilter(wrappedRequest, wrappedResponse);
// TODO ======================================== 加密响应报文 ========================
// 获取响应报文的字节数组
String string = wrappedResponse.getResponseData();
System.out.println("====================返回的报文数据:" + string);
// 对响应报文进行处理,例如加密、压缩等
bodyStr = aes.encrypt(string);
System.out.println("====================返回的加密报文数据:" + bodyStr);
// 将处理后的响应报文写回到 response
response.getOutputStream().write(bodyStr.getBytes(StandardCharsets.UTF_8));
}

需要注意的问题:

  1. request.getInputStream() 只能读一次,并且在过滤器链里面使用,处理完请求报文,还要在塞回去。
  2. 请求报文和响应报文的二次封装通过内部类 EncryptingResponseWrapperCustomHttpServletRequestWrapper 实现
  3. 对于加密和解密使用的RSA,AES以及计算哈希值的算法,服务端和客户端要保证使用一致的密钥格式,即 加密算法(RSA, AES),加密模式(ECB,CBC等),加密补码方式(PKCS1_PADDING, PKCS5_PADDING) 要保持一致,开发中出现的加解密错误大都是这里的问题。

下面为过滤器中处理加密解密完整的代码. 关于算法部分这里没有展示

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
import com.ruoyi.framework.security.filter.tool.AES;
import com.ruoyi.framework.security.filter.tool.CipherTypeEnums;
import com.ruoyi.framework.security.filter.tool.RSAFacade;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.time.Instant;

@Component
@Slf4j
public class SecurityFilter extends OncePerRequestFilter {

private static final String API_PATH_PATTERN = "/hot......eePartyInterfaceCRM/"; // 指

private static final String RSA_PRIVATE_KEY =
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDaEFdIynY8WbH8" +
"...................0W";

// 接口响应失效时间
private static final long REQUEST_EXPIRY_TIME = 30; // seconds

/**
* 加密接口的处理(RSA+AES + )
* @param request
* @param response
* @param filterChain
*/
@SneakyThrows
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {


// 获取请求的路径
String requestUri = request.getRequestURI();
// todo 需要注意 `request.getInputStream()` 只能读一次,所以在开头读取
String bodyStr = getRequestBody(request);

// 只处理特定的路由
if (!requestUri.startsWith(API_PATH_PATTERN)) {
filterChain.doFilter(request, response);
return;
}

// todo ============================= 请求报文的解密处理 ==============================

// 通过 RSA公钥要加的 AES密钥
String secretHeader = request.getHeader("X-Secret");
// 随机数
String nonceHeader = request.getHeader("X-Nonce");
// 时间戳
String timestampHeader = request.getHeader("X-Timestamp");
// 签名
String signatureHeader = request.getHeader("X-Signature");

if (secretHeader == null || nonceHeader == null || timestampHeader == null || signatureHeader == null) {
log.error("请求报文不符合要求!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "signature verification error");
return;
}
// 加载私钥
PrivateKey privateKey = loadPrivateKey(RSA_PRIVATE_KEY);
System.out.println("================= 加密的密钥:"+ secretHeader);
// 通过私钥解密客户端用公钥加密的 AES 密钥
String decrypt = RSAFacade.decrypt(CipherTypeEnums.RSA_PKCS1, secretHeader, privateKey);
System.out.println("================= 解密的密钥:"+ decrypt);

// 比较时间戳
long timestamp = Long.parseLong(timestampHeader);
long currTimestamp = Instant.now().getEpochSecond();

if (currTimestamp - timestamp > REQUEST_EXPIRY_TIME) {
log.error("时间戳异常!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "expired request");
return;
}

// HMAC校验 校验签名
String message = buildMessage(request, secretHeader, nonceHeader, timestampHeader,bodyStr);
System.out.println("=========================: 消息"+ message);
String computedSignature = null;
// 生成HMAC校验签名
computedSignature = toHMAC_SHA256( message,decrypt);
System.out.println("========================== 生成的签名:" + computedSignature);

if (!computedSignature.equals(signatureHeader)) {
log.error("签名不一致!");
response.sendError(HttpStatus.BAD_REQUEST.value(), "signature verification error");
return;
}



System.out.println("================加密的报文数据 requestBody: " + bodyStr);
String encryptedBody = null;
// 通过 AES 解密报文
AES aes = AES.getInstance(CipherTypeEnums.AES_CBC_PKCS7, decrypt, decrypt);
encryptedBody = aes.decrypt(bodyStr);
System.out.println("===================== 获取到的报文数据:"+ encryptedBody);

// TODO ======================================= 重新封装请求和响应报文 ==================================

CustomHttpServletRequestWrapper wrappedRequest = new CustomHttpServletRequestWrapper(request, encryptedBody.getBytes());
EncryptingResponseWrapper wrappedResponse = new EncryptingResponseWrapper(response);


// 调用过滤器链处理请求
filterChain.doFilter(wrappedRequest, wrappedResponse);

// TODO ======================================== 加密响应报文 ========================
// 获取响应报文的字节数组
String string = wrappedResponse.getResponseData();

System.out.println("====================返回的报文数据:" + string);
// 对响应报文进行处理,例如加密、压缩等
bodyStr = aes.encrypt(string);
System.out.println("====================返回的加密报文数据:" + bodyStr);
// 将处理后的响应报文写回到 response
response.getOutputStream().write(bodyStr.getBytes(StandardCharsets.UTF_8));
}


/**
* 加载服务端私钥
* @param privateKeyString
* @return
* @throws Exception
*/
private PrivateKey loadPrivateKey(String privateKeyString) throws Exception {
System.out.println("==============================加载的私钥:"+ privateKeyString );
return RSAFacade.getPrivateKey(CipherTypeEnums.RSA_PKCS1, privateKeyString);

}


/**
* 生成消息
* @param request
* @param secret
* @param nonce
* @param timestamp
* @param bodyStr
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private String buildMessage(HttpServletRequest request, String secret, String nonce, String timestamp, String bodyStr) throws IOException, NoSuchAlgorithmException {
String method = request.getMethod();

String url = request.getRequestURI().toString();
String bodyHash = getBodyHash(bodyStr);

return method + "\n" + url + "\n" + secret + "\n" + nonce + "\n" + timestamp + "\n" + bodyHash + "\n";
}

/**
* 计算请求报文的哈希值
* @param request
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
*/
private String getBodyHash(String request) throws IOException, NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(request.getBytes());
byte[] hash = md.digest();
return Hex.encodeHexString(hash);
}

/**
* 计算给定字符串(str)基于指定密钥(key)的HMAC - SHA256值
* @param str
* @param key
* @return
* @throws Exception
*/
private String toHMAC_SHA256(String str, String key) throws Exception {
byte[] secret = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(secret, "HmacSHA256");
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
byte[] macData = mac.doFinal(str.getBytes(StandardCharsets.UTF_8));
byte[] hex = (new Hex()).encode(macData);
return new String(hex, StandardCharsets.UTF_8);
}


/**
* 获取请求报文数据,
* @param request
* @return
* @throws IOException
* todo 需要注意 `request.getInputStream()` 只能读一次
*/
public static String getRequestBody(HttpServletRequest request) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
ServletInputStream inputStream = null;
BufferedReader bufferedReader = null;

try {
inputStream = request.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));

char[] charBuffer = new char[128];
int bytesRead;

while ((bytesRead = bufferedReader.read(charBuffer)) != -1) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
if (inputStream != null) {
inputStream.close();
}
}

return stringBuilder.toString();
}
/**
* 响应报文的二次封装处理
*/
private static class EncryptingResponseWrapper extends HttpServletResponseWrapper {
private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private PrintWriter writer = new PrintWriter(outputStream);

public EncryptingResponseWrapper(HttpServletResponse response) {
super(response);
}

@Override
public ServletOutputStream getOutputStream() throws IOException {
return new EncryptingServletOutputStream(outputStream);
}

@Override
public PrintWriter getWriter() throws IOException {
return writer;
}

public String getResponseData() throws IOException {
writer.flush();
return outputStream.toString(StandardCharsets.UTF_8.name());
}
}

private static class EncryptingServletOutputStream extends ServletOutputStream {
private final ByteArrayOutputStream byteArrayOutputStream;

public EncryptingServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) {
this.byteArrayOutputStream = byteArrayOutputStream;
}

@Override
public void write(int b) throws IOException {
byteArrayOutputStream.write(b);
}

@Override
public void write(byte[] b) throws IOException {
byteArrayOutputStream.write(b);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
byteArrayOutputStream.write(b, off, len);
}

@Override
public void flush() throws IOException {
byteArrayOutputStream.flush();
}

@Override
public void close() throws IOException {
byteArrayOutputStream.close();
}

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

@Override
public void setWriteListener(WriteListener writeListener) {

}
}
/**
* 请求报文的二次封装处理
*/

public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {

private final ServletInputStream inputStream;

// 构造器接受 HttpServletRequest 和请求体字节数组
public CustomHttpServletRequestWrapper(HttpServletRequest request, byte[] body) throws IOException {
super(request);
this.inputStream = new ByteArrayServletInputStream(body);
}

@Override
public ServletInputStream getInputStream() throws IOException {
// 返回自定义的 ByteArrayInputStream,读取请求体内容
return inputStream;
}

// 如果需要也可以重写 getReader() 方法,以便读取请求体作为字符流
}

public class ByteArrayServletInputStream extends ServletInputStream {

private final ByteArrayInputStream byteArrayInputStream;

public ByteArrayServletInputStream(byte[] data) {
this.byteArrayInputStream = new ByteArrayInputStream(data);
}

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

@Override
public int read(byte[] b) throws IOException {
return byteArrayInputStream.read(b);
}

@Override
public long skip(long n) throws IOException {
return byteArrayInputStream.skip(n);
}

@Override
public int available() throws IOException {
return byteArrayInputStream.available();
}

@Override
public void close() throws IOException {
byteArrayInputStream.close();
}

@Override
public synchronized void mark(int readlimit) {
byteArrayInputStream.mark(readlimit);
}

@Override
public synchronized void reset() throws IOException {
byteArrayInputStream.reset();
}

@Override
public boolean markSupported() {
return byteArrayInputStream.markSupported();
}

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

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

@Override
public void setReadListener(ReadListener readListener) {

}
}

}

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)



© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

发布于

2025-02-06

更新于

2025-02-10

许可协议

评论
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×