通过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 &#34;/api/gateway/ky_directMemory_test&#34; [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.&lt;init&gt;(UnpooledDirectByteBuf.java:64)
15	at io.netty.buffer.UnpooledUnsafeDirectByteBuf.&lt;init&gt;(UnpooledUnsafeDirectByteBuf.java:41)
16	at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.&lt;init&gt;(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)

参考链接: