好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

SpringCloud gateway request的body验证或修改

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

?

1

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

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

- connection

- keep-alive

- te

- transfer-encoding

- trailer

- proxy-authorization

- proxy-authenticate

- x-application-context

- upgrade

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

解决办法:

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

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,需特殊处理。

以下是主要代码

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

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<HttpMessageReader<?>> messageReaders;

     public 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<String> 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<String>,数据是修改后的body

      */

     private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){

         return (exchange,json)-> {

             AtomicReference<String> 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<DataBuffer> 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


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

1.Requestfilter

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

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

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

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

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

@Component

public class RequestFilter implements GlobalFilter, Ordered {

     @Autowired

     private RedisClientTemplate redisClientTemplate;

     @Override

     public Mono<Void> 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).认证通过,将信息重新写入,不通过则返回异常信息。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

@Override

public Mono<Void> 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<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);

                request = new ServerHttpRequestDecorator(request) {

                    @Override

                    public Flux<DataBuffer> 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<Void> gatewayResponse(String code, String message, ServerWebExchange exchange) {

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

     ServerHttpResponse response = exchange.getResponse();

     BaseRespVo<T> 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):

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@Override

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

  ServerHttpRequest serverHttpRequest = exchange.getRequest();

  String method = serverHttpRequest.getMethodValue();

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

   ServerHttpResponse response = exchange.getResponse();

   BaseRespVo<T> 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):

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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

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

private String securityUserName;

/**授权访问密码*/

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

private String securityUserPassword;

@Override

public Mono<Void> 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);

}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。

原文链接:https://blog.csdn.net/shuoshuo132/article/details/88798678

查看更多关于SpringCloud gateway request的body验证或修改方式的详细内容...

  阅读:20次