解决spring cloud gateway 获取body内容并修改的问题

网友投稿 1340 2023-03-05

解决spring cloud gateway 获取body内容并修改的问题

解决spring cloud gateway 获取body内容并修改的问题

之前写过一篇文章,如何获取body的内容。

Spring Cloud Gateway获取body内容,不影响GET请求

确实能够获取所有body的内容了,不过今天终端同学调试接口的时候和我说,遇到了400的问题,报错是这样的HTTP method names must be tokens,搜了一下,都是说https引起的。可我的项目还没用https,排除了。

想到是不是因为修改了body内容导致的问题,试着不修改body的内容,直接传给微服务,果然没有报错了。

问题找到,那就好办了,肯定是我新构建的REQUEST对象缺胳膊少腿了,搜索一通之后发现一篇大牛写的文章:

Spring Cloud Gateway(读取、修改 Request Body)

这里要再次表扬一下古哥,同样是中文文章,度娘却搜不到

不过文章中的spring cloud版本是

Spring Cloud: Greenwich.RC2

我本地是最新的Release版本RS3,并不能完全照搬过来,不过算是给了很大的启发(如何获取body以及重构)

下面给出我的代码

网关中对body内容进行解密然后验签

/**

* @author tengdj

* @date 2019/8/13 11:08

* 设备接口验签,解密

**/

@Slf4j

public class TerminalSignFilter implements GatewayFilter, Ordered {

private static final String AES_SECURTY = "XXX";

private static final String MD5_SALT = "XXX";

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

exchange.getAttributes().put("startTime", System.currentTimeMillis());

if (exchange.getRequest().getMethod().equals(HttpMethod.POST)) {

//重新构造request,参考ModifyRequestBodyGatewayFilterFactory

ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());

MediaType mediaType = exchange.getRequest().getHeaders().getContentType();

//重点

Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {

//因为约定了终端传参的格式,所以只考虑json的情况,如果是表单传参,请自行发挥

if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || MediaType.APPLICATION_JSON_UTF8.isCompatibleWith(mediaType)) {

JSONObject jsonObject = JSONUtil.toJO(body);

String paramStr = jsonObject.getString("param");

String newBody;

TzXtX try{

newBody = verifySignature(paramStr);

}catch (Exception e){

return processError(e.getMessage());

}

return Mono.just(newBody);

}

return Mono.empty();

});

BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);

HttpHeaders headers = new HttpHeaders();

headers.putAll(exchange.getRequest().getHeaders());

//猜测这个就是之前报400错误的元凶,之前修改了body但是没有重新写content length

headers.remove("Content-Length");

//MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage,只不过CachedBodyOutputMessage不是公共的

MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);

return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {

ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);

return returnMono(chain, exchange.mutate().request(decorator).build());

}));

} else {

//GET 验签

MultiValueMap map = exchange.getRequest().getQueryParams();

if (!CollectionUtils.isEmpty(map)) {

String paramStr = map.getFirst("param");

try{

verifySignature(paramStr);

}catch (Exception e){

return processError(e.getMessage());

}

}

return returnMono(chain, exchange);

}

}

@Override

public int getOrder() {

return 1;

}

private Mono returnMono(GatewayFilterChain chain,ServerWebExchange exchange){

return chain.filter(exchange).then(Mono.fromRunnable(()->{

Long startTime = exchange.getAttribute("startTime");

if (startTime != null){

long executeTime = (System.currentTimeMillis() - startTime);

logTzXtX.info("耗时:{}ms" , executeTime);

log.info("状态码:{}" , Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());

}

}));

}

private String verifySignature(String paramStr) throws Exception{

log.info("密文{}", paramStr);

String dParamStr;

try{

dParamStr = AESUtil.decrypt(paramStr, AES_SECURTY);

}catch (Exception e){

throw new Exception("解密失败!");

}

log.info("解密得到字符串{}", dParamStr);TzXtX

String signature = SignUtil.sign(dParamStr, MD5_SALT);

log.info("重新加密得到签名{}", signature);

JSONObject jsonObject1 = JSONUtil.toJO(dParamStr);

if (!jsonObject1.getString("signature").equals(signature)) {

throw new Exception("签名不匹配!");

}

return jsonObject1.toJSONString();

}

private Mono processError(String message) {

/*exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);

return exchange.getResponse().setComplete();*/

log.error(message);

return Mono.error(new Exception(message));

}

ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, MyCachedBodyOutputMessage outputMessage) {

return new ServerHttpRequestDecorator(exchange.getRequest()) {

public HttpHeaders getHeaders() {

long contentLength = headers.getContentLength();

HttpHeaders httpHeaders = new HttpHeaders();

httpHeaders.putAll(super.getHeaders());

if (contentLength > 0L) {

httpHeaders.setContentLength(contentLength);

} else {

httpHeaders.set("Transfer-Encoding", "chunked");

}

return httpHeaders;

}

public Flux getBody() {

return outputMessage.getBody();

}

};

}

}

代码到这里就结束了,希望看到的朋友可以少走点弯路,少踩点坑。

补充知识:springcloud gateway之addRequestParameter详细使用及踩坑注意

SpringCloud的网关gateway提供了多个内置Filter,其中addRequestHeader是添加header的,这个无坑,比较简单。还有一个添加参数的,addRequestParameter,这个就有点问题了。具体往下看。

版本如下,请注意Springboot版本,这是本篇Post请求异常的关键。

1 对应的uri只能是get请求

看一个简单的示例,addRequestParameter,我们匹配/addParam请求,并将请求转发至http://localhost:8888/header

这个是8888端口的服务

如果发起Get请求到网关,那么可以正常请求,一切OK。此时,调用发起方和最终的服务提供方都是Get请求,没有问题。

如果发起的请求是Get,但是服务提供方是如下的Post。

注意,这里我用了PostMapping,然后分别启动两个工程,再访问localhost:8080/addParam,而后会报错,这个也可以理解。

但是,如果调用发起方和服务提供方都是Post请求,理论上应该也是OK的。

但是事实上不是的

网关程序会报错如下:

这个就很尴尬了,作为一个网关,居然在代理非Get请求时出现异常,必然是不能容忍的。

经过一番探索,发现这是Springboot不同版本的原因导致,在Springboot2.0.5之前,不存在该问题,之后就有这种问题了。需要加以注意,解决方案会在下一篇写。

2 添加的参数value值必须合法(不能含有空格)

上面已经知道了,addRequestParameter对应的后端请求是Get型,那么明显添加的parameter只能是Get请求支持的,能在浏览器地址栏直接敲上去合法的。

这里,我将value的值变成带空格的,然后去访问后端的服务。

然后会发现控制台报错,Invalid URI query。这是因为get请求的value值不能含有非法字符.

同理

像这样的,后台接收的是

如果是这样的参数

后台这样

结果是:

这样就可以添加多个parameter了。

同时添加header和parameter

结束了addRequestParameter的说明,我们可以来看看,假如某个path,既想addHeader,又想addParameter,而系统的这两个方法,都是一个path只能搭配一个add的filter,即便写了两个也不生效,如

结果就只有header被打印了

那么就是想同时添加header和parameter该怎么办呢。

貌似通过java代码是无法实现了,好在可以通过yml配置来实现。

spring:

cloud:

gateway:

routes:

- id: header

uri: http://localhost:8888/header

filters:

- AddRequestHeader=NewHeader, Bar

- AddRequestParameter=NewParam, Param

predicates:

- Path=/header

在yml就可以在filters里,添加多个filter了,注意不要写错了filter的名字。

可以看到结果

发现header和param都传过来了。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信电脑上怎么打开小程序(微信电脑上怎么打开小程序功能)
下一篇:小程序渲染(小程序渲染层错误)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~