body Verification or Modification of SpringCloud gateway request
- 2021-11-01 03:20:54
- OfStack
body Verification or Modification of SpringCloud gateway request
Subsequent versions have added the following filters
org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter
The following headers will be removed by default (the purpose of this is not known yet)
- connection
- keep-alive
- te
- transfer-encoding
- trailer
- proxy-authorization
- proxy-authenticate
- x-application-context
- upgrade
As a result, the transfer-encoding header that we added when we override the getHeaders method below is removed, resulting in the failure to parse body.
Solution:
Configure a custom header removal list in an yml file
spring:
cloud:
filter:
remove-hop-by-hop:
headers:
- connection
- keep-alive
- te
- trailer
- proxy-authorization
- proxy-authenticate
- x-application-context
- upgrade
Links are visible to the source code, and dynamic routing configuration can be realized: https://github.com/SingleTigger/SpringCloudGateway-Nacos-Demo
---Original----
Usually in business, we need to modify the request parameters in the gateway (note that the following is only for requests with body), and one is provided in springcloud and gateway
ModifyRequestBodyGatewayFilterFactory filter, looked at 1 under its implementation, need to specify the input type and output type, more limited.
I implemented an interceptor with reference to it myself
Note: The uploaded file also has a request body, which needs special treatment.
Here's the main code
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());
}));
};
}
/**
* Modify body
* @return apply Return Mono<String> The data is modified body
*/
private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){
return (exchange,json)-> {
AtomicReference<String> result = new AtomicReference<>();
json.subscribe(
value -> {
//value Is the request body , modified here
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 Get post Request Body (request body) Incomplete Solution
As a gateway service, Spring Cloud Gateway forwards requests through gateway. Before the request arrives at the back-end service, we can preprocess it through filter, such as the legitimacy of the request and the verification of merchants.
For example, we add merchant ID (merId) and merchant KEY (merkey) to the request body to verify the legitimacy of the request. But if the content of the request is too long, such as the file storage request converted to base64. At this point we get body content in filter will be truncated (too long Body will be truncated). At present, there is no good solution online.
springboot and Cloud versions are as follows;
版本 | |
---|---|
springboot | 2.0.8.RELEASE |
springcloud | Finchley.SR2 |
Here is a solution, and the relevant code is as follows:
1.RequestfilterWe use the Gobalfilter of the Gateway Gateway to set up our first filter to filter all requests.
1) Through WebFlux of Spring 5, we use bodyToMono method to convert the response content into an String-like object, and the final result is Mono object
2). bodyToMono method We can get the full body content and return String.
3). We generate a 1-only token (via UUID) and put token into the requested header.
4). Store the obtained complete body content in redis.
@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();
// Toward headers Put in token Information
ServerHttpRequest serverHttpRequest =exchange.getRequest().mutate().header("token", token)
.build();
// Will the current request Become change Object
ServerWebExchange build = exchange.mutate().request( serverHttpRequest ).build();
return req.bodyToMono( String.class ).map( str -> {
redisClientTemplate.setObjex( "microservice:gateway:".concat( token ), 180, str );
MySlf4j.textInfo( " Request parameter :{0}", str );
return str;
} ).then( chain.filter( build ) );
}
@Override
public int getOrder() {
return 0;
}
}
2.MerchantAuthFilter
Establish a merchant authentication filter, and the relevant codes are as follows:
1). Get the token stored in the headers.
2). Get our body content stored in redis through token (blocking operations cannot be used in WebFlux, which is currently thought of).
3). After obtaining the complete body content, we can carry out the corresponding merchant authentication operation.
4). If the authentication is passed, the information will be rewritten, and if it is not passed, the abnormal information will be returned.
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/** Verify that the merchant has permission to access */
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 {
// Merchant authentication
BaseRespVo<?> baseRespVo = merchantAuthService.checkMerchantAuth( baseReqVo );
if (MicroserviceConstantParamUtils.RESULT_CODE_SUCC.equals( baseRespVo.getCode() )) {
// If the verification is successful, rewrite the information to avoid request After information consumption, the follow-up cannot be obtained from request The problem of obtaining information
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;
}
};
// Encapsulation request , pass it to the next 1 Grade
return chain.filter(exchange.mutate().request(request).build());
} else {
// If the verification is unsuccessful, a prompt message will be returned
return gatewayResponse( baseRespVo.getCode(), baseRespVo.getMessage(), exchange );
}
} catch (MicroserviceServiceException ex) {
// If the verification is unsuccessful, a prompt message will be returned
MySlf4j.textError( " Merchant access authorization verification exception, exception code :{0}, Exception information :{1}, Anomaly {2}", ex.getCode(), ex.getMessage(), ex );
return gatewayResponse( ex.getCode(), ex.getMessage(), exchange );
} catch (Exception ex) {
MySlf4j.textError( " Merchant Access Authentication Service Exception :{0}", LogUtil.ExceptionToString( ex ) );
return gatewayResponse( MicroserviceException.ERR_100000, " System anomaly ", exchange );
} finally {
redisClientTemplate.del( "microservice:gateway:".concat( token ) );
}
}
/** Data stream processing method */
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;
}
/** Gateway request response */
private Mono<Void> gatewayResponse(String code, String message, ServerWebExchange exchange) {
// If the verification is unsuccessful, a prompt message will be returned
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 );
// Specify the code, otherwise it will be garbled in Chinese in the browser
response.getHeaders().add( "Content-Type", "text/plain;charset=UTF-8" );
return response.writeWith( Mono.just( buffer ) );
}
@Override
public int getOrder() {
return 1;
}
In addition, we can also implement request filtering and OAUTH authorization through GlobalFilter. The relevant codes are as follows:
Request Mode Validation Filter (RequestAuthFilter):
@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, " Illegal request ", null);
byte[] bits = JsonUtil.toJson(baseRespVo).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// Specify the code, otherwise it will be garbled in Chinese in the browser
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
OAUTH Authorization Filter (OAuthSignatureFilter):
/** Authorized access user name */
@Value("${spring.security.user.name}")
private String securityUserName;
/** Authorized access password */
@Value("${spring.security.user.password}")
private String securityUserPassword;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**oauth Authorization */
String auth = securityUserName.concat(":").concat(securityUserPassword);
String encodedAuth = new sun.misc.BASE64Encoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + encodedAuth;
// Toward headers Play authorization information in China
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header("Authorization", authHeader)
.build();
// Will the current request Become change Object
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build);
}