通过SpringGateway对接口请求进行加解密
有个需求,要求在前端调用接口时,将请求数据与响应数据做加密。做了一下调研,可以用下面的方式来实现:
public class EncryptGatewayFilter implements GlobalFilter, Ordered {
private static final String BODY_ENCRYPT_HEADER = "X-ENCRYPTED";
private static final String ENCRYPT_VERSION_1 = "1.0";
@Override
public int getOrder() {
return -99;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 请求头中标识了该请求是加密过的
HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
String bodyEncryptHeader = httpHeaders.getFirst(BODY_ENCRYPT_HEADER);
if (bodyEncryptHeader != null && bodyEncryptHeader.equals(ENCRYPT_VERSION_1)) {
// 这里可以处理一些自定义的逻辑,并且将参数传到后续的方法中
CustomParam step1Param = this.prepareCustomParam();
// 处理加密的请求
return processEncryptRequest(exchange, chain, step1Param);
}
// 处理未加密的请求
return chain.filter(exchange);
}
private Mono<Void> processEncryptRequest(ServerWebExchange exchange, GatewayFilterChain chain, CustomParam step1Param) {
// 这里可以做一些前置校验,确定是否需要对请求解密
boolean needProcess = checkIfNeedProcess(step1Param);
if (needProcess) {
try {
// 这里可以处理 step1Param,并且将生成下个阶段需要用的参数 step2Param, 传到后续的方法中
CustomParam step2Param = this.handleCustomParam(step1Param);
// 修改请求内容
return new ModifiedRequestDecorator(new Config()
// 可以移除表示加密的请求头
.addHeaderToRemove(BODY_ENCRYPT_HEADER)
// 设置 request 重写方法,解密请求
.setRewriteFunction(String.class, String.class, (ex, requestData)
-> Mono.just(decryptRequestBody(requestData, step2Param))
)).filter(exchange.mutate().response(
// 修改响应内容
new ModifiedResponseDecorator(exchange, new Config()
// 添加响应头,告知调用者该响应已加密
.addHeaderToAdd(BODY_ENCRYPT_HEADER, ENCRYPT_VERSION_1)
// 设置 response 重写方法,加密响应
.setRewriteFunction(String.class, String.class, (ex, responseData)
-> Mono.just(encryptResponseBody(responseData, step2Param))
))).build(), chain);
} catch (Exception e) {
log.error("Failed to process the request body", e);
// 处理失败,返回错误的响应
return buildFailResult(exchange.getResponse(), HttpStatus.BAD_REQUEST);
}
}
// 验证未通过,则不处理,直接将原始请求传到链条中的下一个处理器
return chain.filter(exchange);
}
private String decryptRequestBody(Object originalRequest, CustomParam step2Param) {
try {
// 这里需要实现解密的方法
...
return decryptedRequest;
} catch (Exception e) {
log.warn("Error when decrypting request.", e);
return null;
}
}
private String encryptResponseBody(String originalResponse, CustomParam step2Param) {
try {
// 这里需要实现加密的方法
...
return oencryptedResponse;
} catch (Exception e) {
log.warn("Error when encrypting response.", e);
return null;
}
}
}
其中 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 之后就解决了。
reactor.netty.ReactorNetty$InternalNettyException: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 32768 byte(s) of direct memory (used: 5222041, max: 5242880)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ com.dtstack.gateway.log.RequestLoggingFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.cloud.sleuth.instrument.web.TraceWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP POST "/api/gateway/ky_directMemory_test" [ExceptionHandlingWebHandler]
Stack trace:
Caused by: io.netty.util.internal.OutOfDirectMemoryError: failed to allocate 32768 byte(s) of direct memory (used: 5222041, max: 5242880)
at io.netty.util.internal.PlatformDependent.incrementMemoryCounter(PlatformDependent.java:754)
at io.netty.util.internal.PlatformDependent.allocateDirectNoCleaner(PlatformDependent.java:709)
at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.allocateDirect(UnpooledUnsafeNoCleanerDirectByteBuf.java:30)
at io.netty.buffer.UnpooledDirectByteBuf.<init>(UnpooledDirectByteBuf.java:64)
at io.netty.buffer.UnpooledUnsafeDirectByteBuf.<init>(UnpooledUnsafeDirectByteBuf.java:41)
at io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf.<init>(UnpooledUnsafeNoCleanerDirectByteBuf.java:25)
at io.netty.buffer.UnsafeByteBufUtil.newUnsafeDirectByteBuf(UnsafeByteBufUtil.java:625)
at io.netty.buffer.PooledByteBufAllocator.newDirectBuffer(PooledByteBufAllocator.java:381)
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:187)
at io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:178)
at io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:139)
at io.netty.channel.DefaultMaxMessagesRecvByteBufAllocator$MaxMessageHandle.allocate(DefaultMaxMessagesRecvByteBufAllocator.java:114)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:150)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:745)
参考链接: