SpringCloud gateway request的body驗(yàn)證或修改方式
SpringCloud gateway request的body驗(yàn)證或修改
后續(xù)版本新增了以下過(guò)濾器
org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter
默認(rèn)會(huì)把以下頭部移除(暫不了解這做法的目的)
- connection
- keep-alive
- te
- transfer-encoding
- trailer
- proxy-authorization
- proxy-authenticate
- x-application-context
- upgrade
從而導(dǎo)致下面我們重寫getHeaders方法時(shí)添加的transfer-encoding頭部移除,導(dǎo)致無(wú)法解析body。
解決辦法:
在yml文件中配置自定義的頭部移除列表
spring:
cloud:
filter:
remove-hop-by-hop:
headers:
- connection
- keep-alive
- te
- trailer
- proxy-authorization
- proxy-authenticate
- x-application-context
- upgrade
源碼可見(jiàn)鏈接,且可實(shí)現(xiàn)動(dòng)態(tài)路由配置:https://github.com/SingleTigger/SpringCloudGateway-Nacos-Demo
------------原文------------
往往業(yè)務(wù)中我們需要在網(wǎng)關(guān)對(duì)請(qǐng)求參數(shù)作修改操作(注意以下只針對(duì)帶有body的請(qǐng)求),springcloud gateway中有提供一個(gè)
ModifyRequestBodyGatewayFilterFactory的filter,看了一下它的實(shí)現(xiàn),需要指定輸入類型和輸出類型,比較局限。
我就參考它自己實(shí)現(xiàn)了一個(gè)攔截器
注意:上傳文件也帶有請(qǐng)求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<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>,數(shù)據(jù)是修改后的body
*/
private BiFunction<ServerWebExchange,Mono<String>,Mono<String>> modifyBody(){
return (exchange,json)-> {
AtomicReference<String> result = new AtomicReference<>();
json.subscribe(
value -> {
//value 即為請(qǐng)求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請(qǐng)求體(request body)不完整解決方案
Spring Cloud Gateway做為網(wǎng)關(guān)服務(wù),通過(guò)gateway進(jìn)行請(qǐng)求轉(zhuǎn)發(fā),在請(qǐng)求到達(dá)后端服務(wù)前我們可以通過(guò)filter進(jìn)行一些預(yù)處理如:請(qǐng)求的合法性,商戶驗(yàn)證等。
如我們?cè)谡?qǐng)求體中添加商戶ID(merId)和商戶KEY(merkey),通過(guò)此來(lái)驗(yàn)證請(qǐng)求的合法性。但是如果我們請(qǐng)求內(nèi)容太長(zhǎng)如轉(zhuǎn)為base64的文件存儲(chǔ)請(qǐng)求。此時(shí)我們?cè)趂ilter獲取body內(nèi)容就會(huì)被截取(太長(zhǎng)的 Body 會(huì)被截?cái)?。目前網(wǎng)上也沒(méi)有好的解決方式。
springboot及Cloud版本如下;
| 版本 | |
|---|---|
| springboot | 2.0.8.RELEASE |
| springcloud | Finchley.SR2 |
這里提供一種解決方式,相關(guān)代碼如下:
1.Requestfilter
我們采用Gateway網(wǎng)關(guān)的Gobalfilter,建立我們的第一個(gè)過(guò)濾器過(guò)濾所有請(qǐng)求。
1).通過(guò)Spring 5 的 WebFlux我們使用bodyToMono方法把響應(yīng)內(nèi)容轉(zhuǎn)換成類 String的對(duì)象,最終得到的結(jié)果是 Mono對(duì)象
2).bodyToMono方法我們可以拿到完整的body內(nèi)容,并返回String。
3).我們生成唯一的token(通過(guò)UUID),并將token放入請(qǐng)求的header中。
4).將獲取到的完整body內(nèi)容,存放到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();
//向headers中放入token信息
ServerHttpRequest serverHttpRequest =exchange.getRequest().mutate().header("token", token)
.build();
//將現(xiàn)在的request變成change對(duì)象
ServerWebExchange build = exchange.mutate().request( serverHttpRequest ).build();
return req.bodyToMono( String.class ).map( str -> {
redisClientTemplate.setObjex( "microservice:gateway:".concat( token ), 180, str );
MySlf4j.textInfo( "請(qǐng)求參數(shù):{0}", str );
return str;
} ).then( chain.filter( build ) );
}
@Override
public int getOrder() {
return 0;
}
}
2.MerchantAuthFilter
建立商戶認(rèn)證過(guò)濾器,相關(guān)代碼如下:
1).獲取存儲(chǔ)在headers中的token。
2).通過(guò)token獲取我們存儲(chǔ)在redis中的body內(nèi)容(WebFlux 中不能使用阻塞的操作,目前想到的是通過(guò)這種方式實(shí)現(xiàn))。
3).獲取到完整的body內(nèi)容后我們就可以進(jìn)行相應(yīng)的商戶認(rèn)證操作。
4).認(rèn)證通過(guò),將信息重新寫入,不通過(guò)則返回異常信息。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/** 驗(yàn)證商戶是否有權(quán)限訪問(wèn) */
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 {
// 商戶認(rèn)證
BaseRespVo<?> baseRespVo = merchantAuthService.checkMerchantAuth( baseReqVo );
if (MicroserviceConstantParamUtils.RESULT_CODE_SUCC.equals( baseRespVo.getCode() )) {
// 若驗(yàn)證成功,將信息重新寫入避免request信息消費(fèi)后后續(xù)無(wú)法從request獲取信息的問(wèn)題
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,傳給下一級(jí)
return chain.filter(exchange.mutate().request(request).build());
} else {
// 若驗(yàn)證不成功,返回提示信息
return gatewayResponse( baseRespVo.getCode(), baseRespVo.getMessage(), exchange );
}
} catch (MicroserviceServiceException ex) {
// 若驗(yàn)證不成功,返回提示信息
MySlf4j.textError( "商戶訪問(wèn)權(quán)限驗(yàn)證異常,異常代碼:{0},異常信息:{1}, 異常{2}", ex.getCode(), ex.getMessage(), ex );
return gatewayResponse( ex.getCode(), ex.getMessage(), exchange );
} catch (Exception ex) {
MySlf4j.textError( "商戶訪問(wèn)權(quán)限驗(yàn)證服務(wù)異常:{0}", LogUtil.ExceptionToString( ex ) );
return gatewayResponse( MicroserviceException.ERR_100000, "系統(tǒng)異常", exchange );
} finally {
redisClientTemplate.del( "microservice:gateway:".concat( token ) );
}
}
/**數(shù)據(jù)流處理方法*/
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;
}
/**網(wǎng)關(guān)請(qǐng)求響應(yīng)*/
private Mono<Void> gatewayResponse(String code, String message, ServerWebExchange exchange) {
// 若驗(yàn)證不成功,返回提示信息
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 );
// 指定編碼,否則在瀏覽器中會(huì)中文亂碼
response.getHeaders().add( "Content-Type", "text/plain;charset=UTF-8" );
return response.writeWith( Mono.just( buffer ) );
}
@Override
public int getOrder() {
return 1;
}
另外我們還可以通過(guò)GlobalFilter實(shí)現(xiàn)請(qǐng)求過(guò)濾,OAUTH授權(quán),相關(guān)代碼如下:
請(qǐng)求方式驗(yàn)證過(guò)濾器(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, "非法請(qǐng)求", null);
byte[] bits = JsonUtil.toJson(baseRespVo).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定編碼,否則在瀏覽器中會(huì)中文亂碼
response.getHeaders().add("Content-Type", "text/plain;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
return chain.filter(exchange);
}
OAUTH授權(quán)過(guò)濾器(OAuthSignatureFilter):
/**授權(quán)訪問(wèn)用戶名*/
@Value("${spring.security.user.name}")
private String securityUserName;
/**授權(quán)訪問(wèn)密碼*/
@Value("${spring.security.user.password}")
private String securityUserPassword;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/**oauth授權(quán)*/
String auth = securityUserName.concat(":").concat(securityUserPassword);
String encodedAuth = new sun.misc.BASE64Encoder().encode(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + encodedAuth;
//向headers中放授權(quán)信息
ServerHttpRequest serverHttpRequest = exchange.getRequest().mutate().header("Authorization", authHeader)
.build();
//將現(xiàn)在的request變成change對(duì)象
ServerWebExchange build = exchange.mutate().request(serverHttpRequest).build();
return chain.filter(build);
}
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
詳解如何使用XML配置來(lái)定義和管理Spring Bean
XML 配置文件是 Spring 中傳統(tǒng)的 Bean 配置方式,通過(guò)定義 XML 元素來(lái)描述 Bean 及其依賴關(guān)系,在 Spring 框架中,Bean 是由 Spring IoC(控制反轉(zhuǎn))容器管理的對(duì)象,本文將詳細(xì)介紹如何使用 XML 配置來(lái)定義和管理 Spring Bean,需要的朋友可以參考下2024-06-06
SpringBoot+Mybatis使用Enum枚舉類型總是報(bào)錯(cuò)No enum constant&n
這篇文章主要介紹了SpringBoot+Mybatis使用Enum枚舉類型總是報(bào)錯(cuò)No enum constant XX問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12
Javaweb實(shí)戰(zhàn)之實(shí)現(xiàn)蛋糕訂購(gòu)系統(tǒng)
隨著網(wǎng)絡(luò)的普及與發(fā)展,網(wǎng)上購(gòu)物逐漸成為一種主流消費(fèi)的方式。這篇文章主要介紹了通過(guò)JavaWeb制作一個(gè)線上蛋糕訂購(gòu)系統(tǒng),文中示例代碼講解詳細(xì),需要的朋友可以參考一下2021-12-12
SpringBoot基于Redis實(shí)現(xiàn)token的在線續(xù)期的實(shí)踐
本文主要介紹了使用Redis實(shí)現(xiàn)JWT令牌在線續(xù)期的方案,通過(guò)在線續(xù)期token,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-12-12
Java?9中List.of()的使用示例及注意事項(xiàng)
Java 9引入了一個(gè)新的靜態(tài)工廠方法List.of(),用于創(chuàng)建不可變的列表對(duì)象,這篇文章主要介紹了Java?9中List.of()的使用示例及注意事項(xiàng)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-03-03
Springboot如何利用攔截器攔截請(qǐng)求信息收集到日志詳解
一些系統(tǒng)經(jīng)常需要關(guān)注用戶請(qǐng)求的具體信息,如用戶信息、請(qǐng)求參數(shù)、響應(yīng)結(jié)果等等,在SpringBoot應(yīng)用中可通過(guò)攔截器的方式統(tǒng)一處理,下面這篇文章主要給大家介紹了關(guān)于Springboot如何利用攔截器攔截請(qǐng)求信息收集到日志的相關(guān)資料,需要的朋友可以參考下2021-08-08
深入理解 Java 中的 Switch 語(yǔ)句示例詳解
在Java編程中,switch語(yǔ)句通過(guò)表達(dá)式值來(lái)執(zhí)行不同代碼塊,本文介紹switch語(yǔ)法、案例、注意事項(xiàng),以及與if語(yǔ)句的對(duì)比,包括基本語(yǔ)法、關(guān)鍵字、表達(dá)式、case常量、break和default的使用,以及如何根據(jù)輸入的字符輸出星期、大小寫轉(zhuǎn)換、成績(jī)判斷和季節(jié)判斷等實(shí)際應(yīng)用場(chǎng)景2024-10-10

