Spring Cloud Gateway 修改响应数据
背景介绍
有个金融类项目,客户对系统安全性比较看重,要求接口请求和响应的数据,都要按特定要求进行加密,防止敏感业务数据被抓包截取。
现在设计流程已经拟定,客户端也解决了如何解密响应数据。服务端还没实现对响应数据进行加密。
抽象出来,本质上要解决的问题是,如何修改响应数据。
问题描述
项目已经使用了Spring Cloud Gateway技术,响应数据可以在网关拦截。
现在的问题是,如何修改响应数据。
关键词:spring cloud gateway modify response body
解决方案
spring cloud gateway 已经提供了修改响应体的示例ModifyResponseBodyGatewayFilterFactory
示例代码内容如下:
ModifyResponseBodyGatewayFilterFactory
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.cloud.gateway.filter.factory.rewrite; import java.util.Map; import org.reactivestreams.Publisher; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.cloud.gateway.support.BodyInserterContext; import org.springframework.cloud.gateway.support.CachedBodyOutputMessage; import org.springframework.cloud.gateway.support.DefaultClientResponse; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseCookie; import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public class ModifyResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyResponseBodyGatewayFilterFactory.Config> { private final ServerCodecConfigurer codecConfigurer; public ModifyResponseBodyGatewayFilterFactory(ServerCodecConfigurer codecConfigurer) { super(ModifyResponseBodyGatewayFilterFactory.Config.class); this.codecConfigurer = codecConfigurer; } public GatewayFilter apply(ModifyResponseBodyGatewayFilterFactory.Config config) { return new ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter(config); } public static class Config { private Class inClass; private Class outClass; private Map<String, Object> inHints; private Map<String, Object> outHints; private String newContentType; private RewriteFunction rewriteFunction; public Config() { } public Class getInClass() { return this.inClass; } public ModifyResponseBodyGatewayFilterFactory.Config setInClass(Class inClass) { this.inClass = inClass; return this; } public Class getOutClass() { return this.outClass; } public ModifyResponseBodyGatewayFilterFactory.Config setOutClass(Class outClass) { this.outClass = outClass; return this; } public Map<String, Object> getInHints() { return this.inHints; } public ModifyResponseBodyGatewayFilterFactory.Config setInHints(Map<String, Object> inHints) { this.inHints = inHints; return this; } public Map<String, Object> getOutHints() { return this.outHints; } public ModifyResponseBodyGatewayFilterFactory.Config setOutHints(Map<String, Object> outHints) { this.outHints = outHints; return this; } public String getNewContentType() { return this.newContentType; } public ModifyResponseBodyGatewayFilterFactory.Config setNewContentType(String newContentType) { this.newContentType = newContentType; return this; } public RewriteFunction getRewriteFunction() { return this.rewriteFunction; } public <T, R> ModifyResponseBodyGatewayFilterFactory.Config setRewriteFunction(Class<T> inClass, Class<R> outClass, RewriteFunction<T, R> rewriteFunction) { this.setInClass(inClass); this.setOutClass(outClass); this.setRewriteFunction(rewriteFunction); return this; } public ModifyResponseBodyGatewayFilterFactory.Config setRewriteFunction(RewriteFunction rewriteFunction) { this.rewriteFunction = rewriteFunction; return this; } } public class ResponseAdapter implements ClientHttpResponse { private final Flux<DataBuffer> flux; private final HttpHeaders headers; public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) { this.headers = headers; if (body instanceof Flux) { this.flux = (Flux)body; } else { this.flux = ((Mono)body).flux(); } } public Flux<DataBuffer> getBody() { return this.flux; } public HttpHeaders getHeaders() { return this.headers; } public HttpStatus getStatusCode() { return null; } public int getRawStatusCode() { return 0; } public MultiValueMap<String, ResponseCookie> getCookies() { return null; } } public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered { private final ModifyResponseBodyGatewayFilterFactory.Config config; public ModifyResponseGatewayFilter(ModifyResponseBodyGatewayFilterFactory.Config config) { this.config = config; } public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) { public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { Class inClass = ModifyResponseGatewayFilter.this.config.getInClass(); Class outClass = ModifyResponseGatewayFilter.this.config.getOutClass(); MediaType originalResponseContentType = (MediaType)exchange.getAttribute("original_response_content_type"); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(originalResponseContentType); ModifyResponseBodyGatewayFilterFactory.ResponseAdapter responseAdapter = ModifyResponseBodyGatewayFilterFactory.this.new ResponseAdapter(body, httpHeaders); DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults()); Mono modifiedBody = clientResponse.bodyToMono(inClass).flatMap((originalBody) -> { return ModifyResponseGatewayFilter.this.config.rewriteFunction.apply(exchange, originalBody); }); BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass); CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, exchange.getResponse().getHeaders()); return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { long contentLength1 = this.getDelegate().getHeaders().getContentLength(); Flux<DataBuffer> messageBody = outputMessage.getBody(); HttpHeaders headers = this.getDelegate().getHeaders(); if (!headers.containsKey("Transfer-Encoding")) { messageBody = messageBody.doOnNext((data) -> { headers.setContentLength((long)data.readableByteCount()); }); } return this.getDelegate().writeWith(messageBody); })); } public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) { return this.writeWith(Flux.from(body).flatMapSequential((p) -> { return p; })); } }; return chain.filter(exchange.mutate().response(responseDecorator).build()); } public int getOrder() { return -2; } } } 复制代码
实现代码
根据源码示例,可以在网关过滤器添加类似逻辑,实现修改响应数据。
ResponseFilter
@Component @Slf4j public class ResponseFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); URI uri = request.getURI(); String url = uri.getPath(); HttpStatus statusCode = exchange.getResponse().getStatusCode(); if(Objects.equals(statusCode, HttpStatus.BAD_REQUEST) || Objects.equals(statusCode, HttpStatus.TOO_MANY_REQUESTS)){ // 如果是特殊的请求,已处理响应内容,这里不再处理 return chain.filter(exchange); } // 根据具体业务内容,修改响应体 return modifyResponseBody(exchange, chain); } /** * 修改响应体 * @param exchange * @param chain * @return */ private Mono<Void> modifyResponseBody(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(exchange.getResponse()) { public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { MediaType originalResponseContentType = (MediaType)exchange.getAttribute("original_response_content_type"); HttpHeaders httpHeaders = new HttpHeaders(); AtomicBoolean isHttpCodeOK = new AtomicBoolean(true); httpHeaders.setContentType(originalResponseContentType); ResponseAdapter responseAdapter = new ResponseAdapter(body, httpHeaders); HttpStatus statusCode = this.getStatusCode(); // 修改后的响应体 Mono modifiedBody = getModifiedBody(statusCode, isHttpCodeOK, responseAdapter, exchange); // 业务上的开关,表示是否开启加密,如果开启,就需要修改响应体,将响应体数据加密。开关从上下文获取。这里只关心是一个boolean值即可。 Boolean flag; BodyInserter bodyInserter; if(!flag) { // 不需要修改响应数据 bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class); }else { // 需要修改响应数据 // 这里最后采用了 ByteArrayResource 类去处理 bodyInserter = BodyInserters.fromPublisher(modifiedBody, ByteArrayResource.class); } CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, httpHeaders); return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { Flux<DataBuffer> messageBody = outputMessage.getBody(); ServerHttpResponse httpResponse = this.getDelegate(); HttpHeaders headers = httpResponse.getHeaders(); if (!headers.containsKey("Transfer-Encoding")) { messageBody = messageBody.doOnNext((data) -> { headers.setContentLength((long)data.readableByteCount()); }); } if(!isHttpCodeOK.get()){ // 业务处理不是200,说明有异常,设置httpCode状态码是500 httpResponse.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); } return httpResponse.writeWith(messageBody); })); } public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) { return this.writeWith(Flux.from(body).flatMapSequential((p) -> { return p; })); } }; return chain.filter(exchange.mutate().response(responseDecorator).build()); } public class ResponseAdapter implements ClientHttpResponse { private final Flux<DataBuffer> flux; private final HttpHeaders headers; public ResponseAdapter(Publisher<? extends DataBuffer> body, HttpHeaders headers) { this.headers = headers; if (body instanceof Flux) { this.flux = (Flux)body; } else { this.flux = ((Mono)body).flux(); } } public Flux<DataBuffer> getBody() { return this.flux; } public HttpHeaders getHeaders() { return this.headers; } public HttpStatus getStatusCode() { return null; } public int getRawStatusCode() { return 0; } public MultiValueMap<String, ResponseCookie> getCookies() { return null; } } @Override public int getOrder() { return FilterOrderConstant.getOrder(this.getClass().getName()); } private Mono getModifiedBody(HttpStatus httpStatus, AtomicBoolean isHttpCodeOK, ResponseAdapter responseAdapter, ServerWebExchange exchange){ switch (httpStatus){ // 业务上需要特殊处理的状态码 case BAD_REQUEST: case METHOD_NOT_ALLOWED: isHttpCodeOK.set(false); return getMono(HttpCode.BAD_REQUEST, exchange); case INTERNAL_SERVER_ERROR: isHttpCodeOK.set(false); return getMono(HttpCode.SERVER_ERROR, exchange); default: // 主要处理流程 return getNormalBody(isHttpCodeOK, responseAdapter, exchange); } } private Mono getNormalBody(AtomicBoolean isHttpCodeOK, ResponseAdapter responseAdapter, ServerWebExchange exchange){ DefaultClientResponse clientResponse = new DefaultClientResponse(responseAdapter, ExchangeStrategies.withDefaults()); return clientResponse.bodyToMono(String.class).flatMap((originalBody) -> { // 业务上的开关,表示是否开启加密,如果开启,就需要修改响应体,将响应体数据加密。开关从上下文获取。这里只关心是一个boolean值即可。 Boolean flag; ObjectMapper objectMapper = new ObjectMapper(); try { R r = objectMapper.readValue(originalBody, R.class); /** * 异常处理流程 */ if(!r.getCode().equals(HttpCode.SUCCESS.getCode())){ // 业务处理不是200,说明有异常,直接返回对应错误 isHttpCodeOK.set(false); ErrorR errorR = new ErrorR() .setCode(r.getCode()) .setMsg(r.getMsg()); String json = objectMapper.writeValueAsString(errorR); log.info("json = {}", json); if(!flag) { // 不需要加密,则不修改响应体 return Mono.just(json); }else { // 对返回数据进行加密 EncryptionUtil.encrypt(json, key) // 具体加密逻辑不再阐述,这里可以理解成string 转成 byte[] 处理 byte[] encrypt = EncryptionUtil.encrypt("{}", key); ByteArrayResource byteArrayResource = new ByteArrayResource(encrypt); // 修改响应体,使用 byteArrayResource 封装 return Mono.just(byteArrayResource); } } // 业务处理是200,截取data内容 Object data = r.getData(); if(null == data){ // 返回数据如果为空,则返回空对象 if(!flag) { // 不需要加密,则不修改响应体 return Mono.just("{}"); }else { // 对返回数据进行加密 EncryptionUtil.encrypt(json, key) // 具体加密逻辑不再阐述,这里可以理解成string 转成 byte[] 处理 byte[] encrypt = EncryptionUtil.encrypt("{}", key); ByteArrayResource byteArrayResource = new ByteArrayResource(encrypt); return Mono.just(byteArrayResource); } } /** * 主要处理流程 */ String json = objectMapper.writeValueAsString(data); if(!flag) { // 不需要加密,则不修改响应体 return Mono.just(json); }else { // 对返回数据进行加密 EncryptionUtil.encrypt(json, key) // 具体加密逻辑不再阐述,这里可以理解成string 转成 byte[] 处理 byte[] encrypt = EncryptionUtil.encrypt("{}", key); ByteArrayResource byteArrayResource = new ByteArrayResource(encrypt); // 修改响应体,使用 byteArrayResource 封装 return Mono.just(byteArrayResource); } }catch (Exception e){ e.printStackTrace(); log.error("convert originalBody error: " + e); return Mono.just(originalBody); } }); } private Mono getMono(HttpCode code, ServerWebExchange exchange){ ObjectMapper objectMapper = new ObjectMapper(); ErrorR errorR = new ErrorR() .setCode(code.getCode()) .setMsg(code.getValue()); try { String json = objectMapper.writeValueAsString(errorR); log.info("json = {}", json); // 开关从上下文获取 if(!flag) { // 不需要加密,则不修改响应体 return Mono.just(json); }else { // 对返回数据进行加密 EncryptionUtil.encrypt(json, key),这里不用管方法具体逻辑, 可以当做 string 转成 byte[] byte[] encrypt = EncryptionUtil.encrypt(json, key); ByteArrayResource byteArrayResource = new ByteArrayResource(encrypt); // 修改响应体,使用 byteArrayResource 封装 return Mono.just(byteArrayResource); } } catch (Exception e) { e.printStackTrace(); log.error("get mono error: " + e); return Mono.just("\"code\": 500, \"msg\":\"error\""); } } }
作者:隔壁的班主任
链接:https://juejin.cn/post/7038779004169486343