Spring Cloud 网关(GlobalFilter)拦截响应体

2021-10-25 18:17:17 浏览数 (1)

*需求场景:需要对返回的手机号等敏感信息进行脱敏处理*

用到:Hutool,JsonPath

代码语言:javascript复制
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.Option;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;

@Slf4j
@Component
public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered {
    Configuration conf = Configuration.builder()
            .options(Option.AS_PATH_LIST).build();

    @Override
    public int getOrder() {
        return FilterOrderEnum.GATEWAY_CONTEXT_FILTER.getOrder();
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return chain.filter(exchange.mutate().response(responseDecorator(exchange)).build());
    }

    private ServerHttpResponseDecorator responseDecorator(ServerWebExchange exchange) {
        return new ServerHttpResponseDecorator(exchange.getResponse()) {
            ServerHttpResponse serverHttpResponse = exchange.getResponse();
            DataBufferFactory bufferFactory = serverHttpResponse.bufferFactory();

            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                return super.writeWith(DataBufferUtils.join(Flux.from(body))
                        .map(dataBuffer -> {
                            // 获取响应体
                            byte[] content = new byte[dataBuffer.readableByteCount()];
                            dataBuffer.read(content);
                            DataBufferUtils.release(dataBuffer);
                            return content;
                        }).flatMap(bytes -> {
                            // 对响应体进行业务判断(返回值是OK,格式为JSON)
                            if (exchange.getResponse().getStatusCode().equals(HttpStatus.OK)
                                    && exchange.getResponse().getHeaders().get(HttpHeaders.CONTENT_TYPE).get(0).equals(MediaType.APPLICATION_JSON_UTF8.toString())) {
                                // 将响应体转换为String
                                String bodyString = new String(uncompress(bytes), StandardCharsets.UTF_8);
                                log.info("bodyString: {}", bodyString);
                                // 进行业务处理
                                // TODO 调用业务处理方法
                                // 读取配置文件内容
                                List<String> filterField = null;
                                String dataHandling = dataHandling(responseData, filterField);
                                log.info("dataHandling: {}", dataHandling);
                                // 最后将返回的数据类型转换为byte
                                bytes = dataHandling.getBytes();
                            } else {

                            }
                            return Mono.just(bufferFactory.wrap(bytes));
                        }));
            }

            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }
        };
    }

    public static byte[] uncompress(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        try {
            GZIPInputStream ungzip = new GZIPInputStream(in);
            byte[] buffer = new byte[256];
            int n;
            while ((n = ungzip.read(buffer)) >= 0) {
                out.write(buffer, 0, n);
            }
        } catch (IOException e) {
            log.error("gzip uncompress error.", e);
        }

        return out.toByteArray();
    }

    /*编码 gzip*/
    public static byte[] compress(String str, String encoding) {
        if (str == null || str.length() == 0) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        GZIPOutputStream gzip;
        try {
            gzip = new GZIPOutputStream(out);
            gzip.write(str.getBytes(encoding));
            gzip.close();
        } catch (IOException e) {
            log.error("gzip compress error.", e);
        }
        return out.toByteArray();

    }

    public String dataHandling(String responseData, List<String> filterField) {
        try {
            String content = responseData;
            for (String filterFieldS : filterField) {
                //获取所有的节点
                List<String> pathList = JsonPath.using(conf).parse(responseData).read("$.."   filterFieldS);
                for (String path : pathList) {
                    //获取父节点
                    String parentPath = StrUtil.removeSuffix(path, "['"   filterFieldS   "']");
                    //读取值
                    Object json = JsonPath.parse(content).read(path);
                    if (json != null) {
                        String read = json.toString();
                        //脱敏
                        String mobileMask = mask(read);
                        String keyName = filterFieldS   "Mask";
                        //父节点下添加元素
                        content = JsonPath.parse(content).put(parentPath, keyName, mobileMask).jsonString();
                    }
                }
            }
            return content;
        } catch (Exception e) {
            log.error(responseData, e);
        }
        return null;
    }

    public static String mask(String val) {
        if(StrUtil.isBlank(val)){
            return val;
        }
        if(val.length() < 3){
            return StrUtil.hide(val,1,val.length());
        }
        if(val.length() < 4){
            return StrUtil.hide(val,1,val.length()-1);
        }
        if(PhoneUtil.isMobile(val) || PhoneUtil.isMobileHk(val) || PhoneUtil.isMobileTw(val)){
            return StrUtil.desensitized(val,DesensitizedUtil.DesensitizedType.MOBILE_PHONE);
        }
        if(EnumUtil.isEnum(val)){
            return StrUtil.desensitized(val,DesensitizedUtil.DesensitizedType.EMAIL);
        }
        //TODO 银行卡的工具类hutool我没找到可以自行在网上查找
        return StrUtil.desensitized(val, DesensitizedUtil.DesensitizedType.ID_CARD);
    }
}

[银行卡工具类](https://blog.csdn.net/sinat_27403673/article/details/77340292)

[JsonPath节点使用文档](https://github.com/json-path/JsonPath)

[JsonPath中文文档](https://blog.csdn.net/londa/article/details/117534776)

0 人点赞