通过SpringGateway对接口请求进行加解密
有个需求,要求在前端调用接口时,将请求数据与响应数据做加密。做了一下调研,可以用下面的方式来实现:
1public class EncryptGatewayFilter implements GlobalFilter, Ordered {
2
3 private static final String BODY_ENCRYPT_HEADER = "X-ENCRYPTED";
4 private static final String ENCRYPT_VERSION_1 = "1.0";
5
6 @Override
7 public int getOrder() {
8 return -99;
9 }
10
11 @Override
12 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
13 // 请求头中标识了该请求是加密过的
14 HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
15 String bodyEncryptHeader = httpHeaders.getFirst(BODY_ENCRYPT_HEADER);
16
17 if (bodyEncryptHeader != null && bodyEncryptHeader.equals(ENCRYPT_VERSION_1)) {
18 // 这里可以处理一些自定义的逻辑,并且将参数传到后续的方法中
19 CustomParam step1Param = this.prepareCustomParam();
20
21 // 处理加密的请求
22 return processEncryptRequest(exchange, chain, step1Param);
23 }
24
25 // 处理未加密的请求
26 return chain.filter(exchange);
27 }
28
29 private Mono<Void> processEncryptRequest(ServerWebExchange exchange, GatewayFilterChain chain, CustomParam step1Param) {
30 // 这里可以做一些前置校验,确定是否需要对请求解密
31 boolean needProcess = checkIfNeedProcess(step1Param);
32
33 if (needProcess) {
34 try {
35 // 这里可以处理 step1Param,并且将生成下个阶段需要用的参数 step2Param, 传到后续的方法中
36 CustomParam step2Param = this.handleCustomParam(step1Param);
37
38 // 修改请求内容
39 return new ModifiedRequestDecorator(new Config()
40 // 可以移除表示加密的请求头
41 .addHeaderToRemove(BODY_ENCRYPT_HEADER)
42 // 设置 request 重写方法,解密请求
43 .setRewriteFunction(String.class, String.class, (ex, requestData)
44 -> Mono.just(decryptRequestBody(requestData, step2Param))
45 )).filter(exchange.mutate().response(
46 // 修改响应内容
47 new ModifiedResponseDecorator(exchange, new Config()
48 // 添加响应头,告知调用者该响应已加密
49 .addHeaderToAdd(BODY_ENCRYPT_HEADER, ENCRYPT_VERSION_1)
50 // 设置 response 重写方法,加密响应
51 .setRewriteFunction(String.class, String.class, (ex, responseData)
52 -> Mono.just(encryptResponseBody(responseData, step2Param))
53 ))).build(), chain);
54
55 } catch (Exception e) {
56 log.error("Failed to process the request body", e);
57
58 // 处理失败,返回错误的响应
59 return buildFailResult(exchange.getResponse(), HttpStatus.BAD_REQUEST);
60 }
61
62 }
63
64 // 验证未通过,则不处理,直接将原始请求传到链条中的下一个处理器
65 return chain.filter(exchange);
66 }
67
68 private String decryptRequestBody(Object originalRequest, CustomParam step2Param) {
69 try {
70 // 这里需要实现解密的方法
71 ...
72
73 return decryptedRequest;
74 } catch (Exception e) {
75 log.warn("Error when decrypting request.", e);
76 return null;
77 }
78 }
79
80 private String encryptResponseBody(String originalResponse, CustomParam step2Param) {
81 try {
82 // 这里需要实现加密的方法
83 ...
84
85 return oencryptedResponse;
86 } catch (Exception e) {
87 log.warn("Error when encrypting response.", e);
88 return null;
89 }
90 }
91}
其中 ModifiedRequestDecorator 这个类的实现可以参考 org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory。稍微有点不同的是,ModifyRequestBodyGatewayFilterFactory 这个类是作用于 route,而这里需要的是一个全局过滤器。当然,如果不想自己实现,更简便的办法是只做个代理类,直接调用 ModifyRequestBodyGatewayFilterFactory。稍微有点不同的是,ModifyRequestBodyGatewayFilterFactory的方法。详细的细节可以参考: https://blog.csdn.net/cqyhuaming/article/details/105280720
同理,ModifiedResponseDecorator 的实现可以参考 org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory 这类的逻辑。
上面示例代码中 Config 类,也就是 ModifyRequestBodyGatewayFilterFactory 与 ModifyResponseBodyGatewayFilterFactory 中用到的 config 类,可以仿照他们的实现合并成一个。
但是,后续在使用过程中,Spring Cloud Gateway 运行一段时间之后,不时地出现 OutOfDirectMemoryError 的错误。代码里面对于使用 dataBuffer 的地方都特地添加了释放的逻辑,但是没有起到任何作用,问题依然存在。去网上查了一些资料,发现是 gateway 版本的问题,升级到 Hoxton.SR11 之后就解决了。
1reactor.netty.ReactorNetty$InternalNettyException: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 32768 byte(s) of direct memory (used: 5222041, max: 5242880)
2 Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
3Error has been observed at the following site(s):
4 |_ checkpoint ⇢ com.dtstack.gateway.log.RequestLoggingFilter [DefaultWebFilterChain]
5 |_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
6 |_ checkpoint ⇢ org.springframework.cloud.sleuth.instrument.web.TraceWebFilter [DefaultWebFilterChain]
7 |_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
8 |_ checkpoint ⇢ HTTP POST "/api/gateway/ky_directMemory_test" [ExceptionHandlingWebHandler]
9Stack trace:
10Caused by: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 32768 byte(s) of direct memory (used: 5222041, max: 5242880)
11 at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:754)
12 at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:709)
13 at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.allocateDirect(UnpooledUnsafeNoCleanerDirectByteBuf.java:30)
14 at io.netty.buffer.UnpooledDirectByteBuf.<init>(UnpooledDirectByteBuf.java:64)
15 at io.netty.buffer.UnpooledUnsafeDirectByteBuf.<init>(UnpooledUnsafeDirectByteBuf.java:41)
16 at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.<init>(UnpooledUnsafeNoCleanerDirectByteBuf.java:25)
17 at io.netty.buffer.UnsafeByteBufUtil.newUnsafeDirectByteBuf(UnsafeByteBufUtil.java:625)
18 at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:381)
19 at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
20 at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:178)
21 at io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:139)
22 at io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator$MaxMessageHandle.allocate(DefaultMaxMessagesRecvByteBufAllocator.java:114)
23 at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:150)
24 at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
25 at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
26 at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
27 at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
28 at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
29 at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
30 at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
31 at java.lang.Thread.run(Thread.java:745)
参考链接: