SpringCloud gateway request的body验证或修改方式

网友投稿 709 2022-10-11


SpringCloud gateway request的body验证或修改方式

SpringCloud gateway request的body验证或修改

后续版本新增了以下过滤器

org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter

默认会把以下头部移除(暂不了解这做法的目的)

- connection

- keep-alive

- te

- transfer-encoding

- trailer

- proxy-authorization

- proxy-authenFmYGRbticate

- x-application-context

- upgrade

从而导致下面我们重写getHeaders方法时添加的transfer-encoding头部移除,导致无法解析body。

解决办法:

在yml文件中配置自定义的头部移除列表

spring:

cloud:

filter:

remove-hop-by-hop:

headers:

- connection

- keep-alive

- te

- trailer

- proxy-authorization

- proxy-authenticate

- x-application-context

- upgrade

源码可见链接,且可实现动态路由配置:https://github.com/SingleTigger/SpringCloudGateway-Nacos-Demo

往往业务中我们需要在网关对请求参数作修改操作(注意以下只针对带有body的请求),springcloud gateway中有提供一个

ModifyRequestBodyGatewayFilterFactory的filter,看了一下它的实现,需要指定输入类型和输出类型,比较局限。

我就参考它自己实现了一个拦截器

注意:上传文件也带有请求body,需特殊处理。

以下是主要代码

import org.springframework.cloud.gateway.filter.GatewayFilter;

import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;

import org.springframework.cloud.gateway.support.BodyInserterContext;

import org.springframework.core.io.buffer.DataBuffer;

import org.springframework.http.HttpHeaders;

import org.springframework.http.codec.HttpMessageReader;

import org.springframework.http.server.reactive.ServerHttpRequest;

import org.springframework.http.server.reactive.ServerHttpRequestDecorator;

import org.springframework.stereotype.Component;

import org.springframework.web.reactive.function.BodyInserter;

import org.springframework.web.reactive.function.BodyInserters;

import org.springframework.web.reactive.function.server.HandlerStrategies;

import org.springframework.web.reactive.function.server.ServerRequest;

import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Flux;

import reactor.core.publisher.Mono;

import java.util.List;

import java.util.concurrent.atomic.AtomicReference;

import java.util.function.BiFunction;

/**

* @author chenws

* @date 2019/12/12 09:33:53

*/

@Component

public class CModifyRequestBodyGatewayFilterFactory extends AbstractGatewayFilterFactory {

private final List> messageReaders;

publFmYGRbic CModifyRequestBodyGatewayFilterFactory() {

this.messageReaders = HandlerStrategies.withDefaults().messageReaders();

}

@Override

@SuppressWarnings("unchecked")

public GatewayFilter apply(Object config) {

return (exchange, chain) -> {

ServerRequest serverRequest = ServerRequest.create(exchange,

this.messageReaders);

Mono modifiedBody = serverRequest.bodyToMono(String.class)

.flatMap(originalBody -> modifyBody()

.apply(exchange,Mono.just(originalBody)));

BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,

String.class);

HttpHeaders headers = new HttpHeaders();

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

headers.remove(HttpHeaders.CONTENT_LENGTH);

CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange,

headers);

return bodyInserter.insert(outputMessage, new BodyInserterContext())

.then(Mono.defer(() -> {

ServerHttpRequest decorator = decorate(exchange, headers,

outputMessage);

return chain.filter(exchange.mutate().request(decorator).build());

}));

};

}

/**

* 修改body

* @return apply 返回Mono,数据是修改后的body

*/

private BiFunction,Mono> modifyBody(){

return (exchange,json)-> {

AtomicReference result = new AtomicReference<>();

json.subscribe(

value -> {

//value 即为请求body,在此处修改

result.set(value);

System.out.println(result.get());

},

Throwable::printStackTrace

);

return Mono.just(result.get());

};

}

private ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers,

CachedBodyOutputMessage outputMessage) {

return new ServerHttpRequestDecorator(exchange.getRequest()) {

@Override

public HttpHeaders getHeaders() {

long contentLength = headers.getContentLength();

HttpHeaders httpHeaders = new HttpHeaders();

httpHeaders.putAll(super.getHeaders());

if (contentLength > 0) {

httpHeaders.setContentLength(contentLength);

}

else {

httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");

}

return httpHeaders;

}

@Override

public Flux getBody() {

return outputMessage.getBody();

}

};

}

}

SpringCloud Gateway获取post请求体(request body)不完整解决方案

Spring Cloud Gateway做为网关服务,通过gateway进行请求转发,在请求到达后端服务前我们可以通过filter进行一些预处理如:请求的合法性,商户验证等。

如我们在请求体中添加商户ID(merId)和商户KEY(merkey),通过此来验证请求的合法性。但是如果我们请求内容太长如转为base64的文件存储请求。此时我们在filter获取body内容就会被截取(太长的 Body 会被截断)。目前网上也没有好的解决方式。

springboot及Cloud版本如下;

版本

springboot

2.0.8.RELEASE

springcloud

Finchley.SR2

这里提供一种解决方式,相关代码如下:

FmYGRb

1.Requestfilter

我们采用Gateway网关的Gobalfilter,建立我们的第一个过滤器过滤所有请求。

1).通过Spring 5 的 WebFlux我们使用bodyToMono方法把响应内容转换成类 String的对象,最终得到的结果是 Mono对象

2).bodyToMono方法我们可以拿到完整的body内容,并返回String。

3).我们生成唯一的token(通过UUID),并将token放入请求的header中。

4).将获取到的完整body内容,存放到redis中。

@Component

public class RequestFilter implements GlobalFilter, Ordered {

@Autowired

private RedisClientTemplate redisClientTemplate;

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

DefaultServerRequest req = new DefaultServerRequest( exchange );

String token = UUID.randomUUID().toString();

//向headers中放入token信息

ServerHttpRequest serverHttpRequest =exchange.getRequest().mutate().header("token", token)

.build();

//将现在的request变成change对象

ServerWebExchange build = exchange.mutate().request( serverHttpRequest ).build();

return req.bodyToMono( String.class ).map( str -> {

redisClientTemplate.setObjex( "microservice:gateway:".concat( token ), 180, str );

MySlf4j.textInfo( "请求参数:{0}", str );

return str;

} ).then( chain.filter( build ) );

}

@Override

public int getOrder() {

return 0;

}

}

2.MerchantAuthFilter

建立商户认证过滤器,相关代码如下:

1).获取存储在headers中的token。

2).通过token获取我们存储在redis中的body内容(WebFlux 中不能使用阻塞的操作,目前想到的是通过这种方式实现)。

3).获取到完整的body内容后我们就可以进行相应的商户认证操作。

4).认证通过,将信息重新写入,不通过则返回异常信息。

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

/** 验证商户是否有权限访问 */

ServerHttpRequest serverHttpRequest = exchange.getRequest();

String token = serverHttpRequest.getHeaders().get( "token" ).get( 0 );

String bodyStr = (String) redisClientTemplate.getObj("microservice:gateway:".concat(token));

BaseReqVo baseReqVo = JsonUtil.fromJson( bodyStr, BaseReqVo.class );

try {

// 商户认证

BaseRespVo> baseRespVo = merchantAuthService.checkMerchantAuth( baseReqVo );

if (MicroserviceConstantParamUtils.RESULT_CODE_SUCC.equals( baseRespVo.getCode() )) {

// 若验证成功,将信息重新写入避免request信息消费后后续无法从request获取信息的问题

URI uri = serverHttpRequest.getURI();

ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();

DataBuffer bodyDataBuffer = stringBuffer(bodyStr);

Flux bodyFlux = Flux.just(bodyDataBuffer);

request = new ServerHttpRequestDecorator(request) {

@Override

public Flux getBody() {

return bodyFlux;

}

};

// 封装request,传给下一级

return chain.filter(exchange.mutate().request(request).build());

} else {

// 若验证不成功,返回提示信息

return gatewayResponse( baseRespVo.getCode(), baseRespVo.getMessage(), exchange );

}

} catch (MicroserviceServiceException ex) {

// 若验证不成功,返回提示信息

MySlf4j.textError( "商户访问权限验证异常,异常代码:{0},异常信息:{1}, 异常{2}", ex.getCode(), ex.getMessage(), ex );

return gatewayResponse( ex.getCode(), ex.getMessage(), exchange );

} catch (Exception ex) {

MySlf4j.textError( "商户访问权限验证服务异常:{0}", LogUtil.ExceptionToString( ex ) );

return gatewayResponse( MicroserviceException.ERR_100000, "系统异常", exchange );

} finally {

redisClientTemplate.del( "microservice:gateway:".concat( token ) );

}

}

/**数据流处理方法*/

private DataBuffer stringBuffer(String value) {

byte[] bytes = value.getBytes( StandardCharsets.UTF_8 );

NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory( ByteBufAllocator.DEFAULT );

DataBuffer buffer = nettyDataBufferFactory.allocateBuffer( bytes.length );

buffer.write( bytes );

return buffer;

}

/**网关请求响应*/

private Mono gatewayResponse(String code, String message, ServerWebExchange exchange) {

// 若验证不成功,返回提示信息

ServerHttpResponse response = exchange.getResponse();

BaseRespVo baseRespVo = ResponseUtils.responseMsg( code, message, null );

byte[] bits = JsonUtil.toJson( baseRespVo ).getBytes( StandardCharsets.UTF_8 );

DataBuffer buffer = response.bufferFactory().wrap( bits );

response.setStatusCode( HttpStatus.UNAUTHORIZED );

// 指定编码,否则在浏览器中会中文乱码

response.getHeaders().add( "Content-Type", "text/plain;charset=UTF-8" );

return response.writeWith( Mono.just( buffer ) );

}

@Override

public int getOrder() {

return 1;

}

另外我们还可以通过GlobalFilter实现请求过滤,OAUTH授权,相关代码如下:

请求方式验证过滤器(RequestAuthFilter):

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

ServerHttpRequest serverHttpRequest = exchange.getRequest();

String method = serverHttpRequest.getMethodValue();

if (!"POST".equals(method)) {

ServerHttpResponse response = exchange.getResponse();

BaseRespVo baseRespVo = ResponseUtils.responseMsg(MicroserviceException.ERR_100008, "非法请求", null);

byte[] bits = JsonUtil.toJson(baseRespVo).getBytes(StandardCharsets.UTF_8);

DataBuffer buffer = response.bufferFactory().wrap(bits);

response.setStatusCode(HttpStatus.UNAUTHORIZED);

//指定编码,否则在浏览器中会中文乱码

response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");

return response.writeWith(Mono.just(buffer));

}

return chain.filter(exchange);

}

OAUTH授权过滤器(OAuthSignatureFilter):

/**授权访问用户名*/

@Value("${spring.security.user.name}")

private String securityUserName;

/**授权访问密码*/

@Value("${spring.security.user.password}")

private String securityUserPassword;

@Override

public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {

/**oauth授权*/

String auth = securityUserName.concat(":").concat(securityUserPassword);

String encodedAuth = new sun.misc.BASE64Encoder().encode(auth.getBytes(Charset.forName("US-ASCII")));

String authHeader = "Basic " + encodedAuth;

//向headers中放授权信息

ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header("Authorization", authHeader)

.build();

//将现在的request变成change对象

ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();

return chain.filter(build);

}


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

上一篇:斗轮机运行过程中各类高发故障无线解决方案(斗轮机的故障原因分析及处理)
下一篇:技术分享| 应急指挥调度平台需要这些技术支撑
相关文章

 发表评论

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