Spring Cloud Gateway 修改响应数据

2,803 阅读5分钟

背景介绍

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

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

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

问题描述

项目已经使用了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\"");
        }
    }
}