阅读 352

Spring Cloud Gateway 修改响应数据

背景介绍

有个金融类项目,客户对系统安全性比较看重,要求接口请求和响应的数据,都要按特定要求进行加密,防止敏感业务数据被抓包截取。

现在设计流程已经拟定,客户端也解决了如何解密响应数据。服务端还没实现对响应数据进行加密。

抽象出来,本质上要解决的问题是,如何修改响应数据。

问题描述

项目已经使用了Spring Cloud Gateway技术,响应数据可以在网关拦截。

现在的问题是,如何修改响应数据。

关键词:spring cloud gateway modify response body

解决方案

spring cloud gateway 已经提供了修改响应体的示例ModifyResponseBodyGatewayFilterFactory

image.png

示例代码内容如下:

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


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐