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-authenticate
- 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<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中。
@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).认证通过,将信息重新写入,不通过则返回异常信息。
@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):
@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):
/**授权访问用户名*/ @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); }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。