gateway中对返回的数据进行处理

gateway中对返回的数据进行处理

  • 背景
    • 1.项目层次

背景

最近公司有个需求是对返回数据进行处理,比如进行数据脱敏。最后在gateway中进行处理。

1.项目层次

根据项目的结构,原本在菜单功能处有对于权限设计的url判断,所以在url后面加了一个正则表达式的字段,例如“/^(1[3-9][0-9])\d{4}(\d{4}$)/:$1****$2”
因为正则表达式,我存储在redis和本地缓存中,表达式中的转义符号一定要注意,我在处理时,处理了转义,所以在页面填写时需要多加


```java
package com.qlisv.qqdznyyglpt.gateway.filter;


import cn.hutool.json.JSONUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.qlisv.qqdznyyglpt.gateway.feign.PermissionsClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
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.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

/**
 * @author 63418
 */
@Slf4j
@Component
public class DataDesensitizationResponseFilter implements GlobalFilter, Ordered {

    @Resource
    private PermissionsClient permissionsClient;

    @Resource
    private RedisTemplate redisTemplate;

    public static final String redisDataDesensitizationKey = "redisDataDesensitizationKey:%s:%s";

    public static Cache<String, Object> cache = CacheBuilder.newBuilder()
            // 初始容量
            .initialCapacity(5)
            // 最大缓存数,超出淘汰
            .maximumSize(50)
            // 过期时间 设置写入3秒后过期
            .expireAfterWrite(60, TimeUnit.SECONDS)
            .build();


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("DataDesensitizationResponseFilter---path={}", exchange.getRequest().getPath());
        if (exchange.getRequest().getMethod() == HttpMethod.OPTIONS) {
            return chain.filter(exchange);
        }
        boolean isExternalRequest = isExternalRequest(exchange);
//        boolean checkContentTypeAndUrl = checkContentTypeAndUrl(exchange);
        if (isExternalRequest) {
            // 如果是外部请求,处理返回的数据
            return processResponse(exchange, chain);
        }

        return chain.filter(exchange);

    }

    private boolean isExternalRequest(ServerWebExchange exchange) {
        // 判断请求是否来自外部
        // 可以根据实际情况进行自定义判断.这个判断基于nginx代理,如果后面有外部程序调用,根据场景修改此处判断,,nginx的配置文件一定要加上X-Nginx-Proxy这个header
        ServerHttpRequest request = exchange.getRequest();
        if (request.getHeaders().containsKey("X-Nginx-Proxy") &&
                request.getHeaders().containsKey("X-Real-IP")) {
            return true;
        }
        return false;
    }

    private Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        try {
            ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
                @Override
                public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                    MediaType contentType = this.getHeaders().getContentType();
                    String contentTypeString = contentType.toString();

                    if (!contentTypeString.startsWith(MediaType.APPLICATION_JSON_VALUE) || !exchange.getResponse().getStatusCode().equals(HttpStatus.OK)) {
                        return super.writeWith(body);
                    }
                    // 异步获取会话中的属性
                    return exchange.getSession()
                            .flatMap(session -> {
                                // 获取会话中的 username 和 tenantId
                                String username = session.getAttribute("username");
                                String tenantId = session.getAttribute("tenantId");
                                String userId = session.getAttribute("userId");
                                // 创建一个Map,包含 username 和 tenantId
                                Map<String, String> name = new HashMap<>();
                                name.put("username", username);
                                name.put("tenantId", tenantId);
                                name.put("userId", userId);
                                // 返回这个Map
                                return Mono.just(name);
                            }).flatMap(nameMap -> {


                                Map<String, String> dataDesensitizationMap = new LinkedHashMap<>();
                                try {
                                    dataDesensitizationMap = getDataDesensitizationMap(nameMap.get("username"), nameMap.get("userId"), nameMap.get("tenantId"));
                                    if (!checkDataDesensitizationMap(dataDesensitizationMap, exchange)) {
                                        return super.writeWith(body);
                                    }
                                } catch (Exception e) {
                                    log.error("[DataDesensitizationResponseFilter] 获取配置和正则表达式异常", e);
                                    return super.writeWith(body);
                                }
                                if (body instanceof Flux) {
                                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                                    Map<String, String> finalDataDesensitizationMap = dataDesensitizationMap;
                                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                                        byte[] newContent = new byte[0];
                                        try {
                                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                                            DataBuffer join = dataBufferFactory.join(dataBuffers);
                                            byte[] content = new byte[join.readableByteCount()];
                                            join.read(content);
                                            DataBufferUtils.release(join);
                                            // 获取响应数据
                                            String responseStr = new String(content, StandardCharsets.UTF_8);

                                            // 获取响应数据
                                            List<String> strings = exchange.getResponse().getHeaders().get(HttpHeaders.CONTENT_ENCODING);
                                            if (!CollectionUtils.isEmpty(strings) && strings.contains("gzip")) {
                                                GZIPInputStream gzipInputStream = null;
                                                try {
                                                    gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(content), content.length);
                                                    StringWriter writer = new StringWriter();
                                                    IOUtils.copy(gzipInputStream, writer, StandardCharsets.UTF_8);
                                                    responseStr = writer.toString();

                                                } catch (IOException e) {
                                                    log.error("====Gzip IO error", e);
                                                } finally {
                                                    if (gzipInputStream != null) {
                                                        try {
                                                            gzipInputStream.close();
                                                        } catch (IOException e) {
                                                            log.error("===Gzip IO close error", e);
                                                        }
                                                    }
                                                }
                                            } else {
                                                responseStr = new String(content, StandardCharsets.UTF_8);
                                            }
                                            newContent = desensitizeJson(responseStr, finalDataDesensitizationMap).getBytes(StandardCharsets.UTF_8);
                                            originalResponse.getHeaders().setContentLength(newContent.length);
                                        } catch (Exception e) {
                                            log.error("responseStr exchange error", e);
                                            throw new RuntimeException(e);
                                        }
                                        return bufferFactory.wrap(newContent);
                                    }));
                                }
                                return super.writeWith(body);
                            });
                }

                @Override
                public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                    return writeWith(Flux.from(body).flatMapSequential(p -> p));
                }
            };
            return chain.filter(exchange.mutate().response(decoratedResponse).build());
        } catch (Exception e) {
            log.error("RewriteResponse error", e);
            return chain.filter(exchange);
        }


    }


    private static String desensitizeJson(String json, Map<String, String> dataDesensitization) {
        // 在这里实现你的 JSON 脱敏逻辑
        // 例如,使用 JSON 库解析 JSON,修改需要脱敏的字段,然后再序列化回 JSON 字符串
        // 返回脱敏后的 JSON 字符串
        String regularExpression = dataDesensitization.get("RegularExpression");
        String[] regularExpressionArr = regularExpression.split(";");
        for (String regularExpressionStr : regularExpressionArr) {
            String[] regularExpressionStrArr = regularExpressionStr.split(":");
            String regularExpressionStrKey = regularExpressionStrArr[0];
            String regularExpressionStrValue = regularExpressionStrArr[1];
            //这个地方的转义字符好坑,,,,
            Pattern pattern = Pattern.compile(StringEscapeUtils.unescapeJava(regularExpressionStrKey));
            Matcher matcher = pattern.matcher(json);
            json = matcher.replaceAll(StringEscapeUtils.unescapeJava(regularExpressionStrValue));
        }
        return json;
    }

    /**
     * 通过本地缓存或者redis缓存获取脱敏规则,规则的刷新在CustomServerAuthenticationSuccessHandler ,登录设置session时刷新
     * @param username
     * @param userId
     * @param tenantId
     * @return
     */
    private Map<String, String> getDataDesensitizationMap(String username, String userId, String tenantId) {
        String key = String.format(redisDataDesensitizationKey, tenantId, userId);
        Map reDataDesensitization = null;
        try {
            reDataDesensitization = (Map) cache.get(key, () -> {
                if (redisTemplate.hasKey(key)) {
                    Object dataDesensitization = redisTemplate.opsForValue().get(key);
                    redisTemplate.expire(key, 60, TimeUnit.MINUTES);
                    return dataDesensitization;
                }
                return null;
            });
            cache.put(key, reDataDesensitization);
            if (reDataDesensitization != null) {
                return reDataDesensitization;
            }
        } catch (Exception e) {
            log.error("[DataDesensitizationResponseFilter] 从缓存获取数据异常", e);
        }

        Map<String, String> dataDesensitization = permissionsClient.findDataDesensitizationByUsername(username, userId, tenantId);
        cache.put(key, dataDesensitization);
        redisTemplate.opsForValue().set(key, dataDesensitization, 60, TimeUnit.MINUTES);
        return dataDesensitization;
    }

    private Boolean checkDataDesensitizationMap(Map<String, String> dataDesensitization, ServerWebExchange exchange) {
        if (dataDesensitization == null) {
            return false;
        }
        if (!dataDesensitization.containsKey("data_desensitization")) {
            return false;
        }
        String data_desensitization = dataDesensitization.get("data_desensitization");
        if ("false".equals(data_desensitization)) {
            return false;
        }
        ServerHttpRequest request = exchange.getRequest();
        // 获取请求的路径
        String path = request.getPath().toString();
        for (String key : dataDesensitization.keySet()) {
            // 如果dataDesensitization Map中的某个键与请求的路径匹配,则返回true
            // spring的路径匹配工具,匹配一些特殊写法
            AntPathMatcher matcher = new AntPathMatcher();
            if (matcher.match(key, path)) {
                // 检查对应路径的值是否为true,如果是则表示需要进行数据脱敏
                dataDesensitization.put("RegularExpression", dataDesensitization.get(key));
                return true;
            }
        }

        return false;
    }

    @Override
    public int getOrder() {
        return -2;
    }

    public static void main(String[] args) {
        String str="{\"customerContacts\":[],\"businessData\":[{\"name\":\"测试\",\"id\":\"ab7025db-e61e-4c4f-8dc9-8bf5539e05ad\",\"value\":[[\"ID\",\"姓名\",\"性别\",\"出生日期\",\"备注\",\"权限\"]]},{\"name\":\"预约\",\"id\":\"f3a99a5d-ec79-4523-a632-2dbc6f6bf83f\",\"value\":[[\"ID\",\"社保\",\"公积金\",\"数据\",\"其他\"],[\"87bccd32-7b48-4cb8-88f2-8eaaf7d15e65\",\"二档\",\"是\",\"2024-03-22 11:40:12\",\"李十四\"]]}],\"customerInfoExt\":{\"id\":null,\"tenantId\":null,\"customerId\":null,\"delStatus\":0,\"field1\":null,\"field2\":null,\"field3\":null,\"field4\":null,\"field5\":null,\"field6\":null,\"field7\":null,\"field8\":null,\"field9\":null,\"field10\":null,\"field11\":null,\"field12\":null,\"field13\":null,\"field14\":null,\"field15\":null,\"field16\":null,\"field17\":null,\"field18\":null,\"field19\":null,\"field20\":null,\"field21\":null,\"field22\":null,\"field23\":null,\"field24\":null,\"field25\":null,\"field26\":null,\"field27\":null,\"field28\":null,\"field29\":null,\"field30\":null,\"field31\":null,\"field32\":null,\"field33\":null,\"field34\":null,\"field35\":null,\"field36\":null,\"field37\":null,\"field38\":null,\"field39\":null,\"field40\":null,\"field41\":null,\"field42\":null,\"field43\":null,\"field44\":null,\"field45\":null,\"field46\":null,\"field47\":null,\"field48\":null,\"field49\":null,\"field50\":null},\"customer\":{\"id\":\"972d7770-0d1a-4c5b-a171-5ab4ff144764\",\"tenantId\":\"8654e94c-f430-4574-ab08-6b4bbbad1e9d\",\"customerName\":\"罗棉\",\"customerCode\":\"1\",\"sex\":0,\"age\":24,\"education\":4,\"birthday\":\"2024-03-22\",\"certificateType\":\"0\",\"certificateNo\":\"511***********1023\",\"maritalStatus\":1,\"accountLocation\":\"广东深圳龙华区民治大道2\",\"accountLocationPostal\":\"632200\",\"currentPostal\":\"456789\",\"address\":\"深圳市龙华区民治街道水围小区\",\"agentId\":\"1002\",\"accountManager\":\"李三\",\"email\":\"3570178990@qq.com\",\"qq\":\"12568900\",\"wechat\":\"ql8563515\",\"weiboNumber\":\"12345123\",\"personalTelephone\":\"17890804789\",\"personalMobilePhone\":\"17890890000\",\"officePhone\":\"13956789012\",\"homePhone\":\"12345678901\",\"emergencyContactPhone\":\"15908765439\",\"otherNumber1\":\"1234567890\",\"otherNumber2\":\"一档\",\"otherNumber3\":null,\"otherNumber4\":null,\"otherNumber5\":null,\"industry\":\"1\",\"industryType\":\"3\",\"companyNature\":\"1\",\"companyName\":\"飞牛公司\",\"companyPost\":\"委员\",\"companySize\":\"1\",\"companyAddress\":\"深圳市龙华区民治街道水围小区\",\"companyPostal\":\"636600\",\"companyPhone\":\"15267890562\",\"workingYears\":1.0,\"annualIncome\":null,\"socialSecurity\":1,\"delStatus\":0,\"creater\":\"jmh\",\"createTime\":\"2024-03-22 11:35:57\",\"updater\":\"jmh\",\"updateTime\":\"2024-03-25 17:15:04\",\"customerType\":1,\"customerBusinessId\":\"2010\",\"sourceFrom\":\"1\"}}";
        System.out.println(str.replaceAll("/^(1[3-9][0-9])\\d{4}(\\d{4}$)/","$1****$2"));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/591258.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【简单讲解下npm常用命令】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《电-氢-混氢天然气耦合的城市综合能源系统低碳优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

政安晨:【Keras机器学习示例演绎】(三十二)—— 在 Vision Transformers 中学习标记化

目录 导言 导入 超参数 加载并准备 CIFAR-10 数据集 数据扩增 位置嵌入模块 变压器的 MLP 模块 令牌学习器模块 变换器组 带有 TokenLearner 模块的 ViT 模型 培训实用程序 使用 TokenLearner 培训和评估 ViT 实验结果 参数数量 最终说明 政安晨的个人主页&…

STM32单片机实战开发笔记-EXIT外部中断检测

嵌入式单片机开发实战例程合集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11av8rV45dtHO0EHf8e_Q0Q?pwd28ab 提取码&#xff1a;28ab EXIT模块测试 功能描述 外部中断/事件控制器由19个产生事件/中断要求的边沿检测器组成。每个输入线可以独立地配置输入类型&a…

政安晨:【Keras机器学习示例演绎】(三十三)—— 知识提炼

目录 设置 构建 Distiller() 类 创建学生和教师模型 准备数据集 培训教师 将教师模型蒸馏给学生模型 从头开始训练学生进行比较 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够…

基于Spring Boot的医疗服务系统设计与实现

基于Spring Boot的医疗服务系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 医疗服务系统首页界面图&#xff0c;公告信息、医疗地图…

快速幂笔记

快速幂即为快速求出一个数的幂&#xff0c;这样可以避免TLE&#xff08;超时&#xff09;的错误。 传送门&#xff1a;快速幂模板 前置知识&#xff1a; 1) 又 2) 代码&#xff1a; #include <bits/stdc.h> using namespace std; int quickPower(int a, int b) {int…

容斥原理以及Nim基础(异或,SG函数)

容斥原理&#xff1a; 容斥的复杂度为O&#xff08;2^m)&#xff0c;所以可以通过&#xff0c;对于实现&#xff0c;一共2^n-1种&#xff0c;我们可以用二进制来实现 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; typedef long long LL; cons…

uniapp 文字转语音(文字播报、语音合成)、震动提示插件 Ba-TTS

简介&#xff08;下载地址&#xff09; Ba-TTS 是一款uniapp语音合成&#xff08;tts&#xff09;插件&#xff0c;支持文本转语音&#xff08;无服务费&#xff09;&#xff0c;支持震动提示。 支持语音合成&#xff0c;文本转语音支持震动&#xff08;可自定义任意震动效果…

【云原生】Docker 实践(四):使用 Dockerfile 文件的综合案例

【Docker 实践】系列共包含以下几篇文章&#xff1a; Docker 实践&#xff08;一&#xff09;&#xff1a;在 Docker 中部署第一个应用Docker 实践&#xff08;二&#xff09;&#xff1a;什么是 Docker 的镜像Docker 实践&#xff08;三&#xff09;&#xff1a;使用 Dockerf…

【LinuxC语言】信号的基本概念与基本使用

文章目录 前言一、信号的概念二、信号的使用2.1 基本的信号类型2.2 signal函数 总结 前言 在Linux环境下&#xff0c;信号是一种用于通知进程发生了某种事件的机制。这些事件可能是由操作系统、其他进程或进程本身触发的。对于C语言编程者来说&#xff0c;理解信号的基本概念和…

36.Docker-Dockerfile自定义镜像

镜像结构 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。 镜像是分层机构&#xff0c;每一层都是一个layer BaseImage层&#xff1a;包含基本的系统函数库、环境变量、文件系统 EntryPoint:入口&#xff0c;是镜像中应用启动的命令 其他&#xff1a;在…

从0开始学习制作一个微信小程序 学习部分(6)组件与事件绑定

系列文章目录 学习篇第一篇我们讲了编译器下载&#xff0c;项目、环境建立、文件说明与简单操作&#xff1a;第一篇链接 第二、三篇分析了几个重要的配置json文件&#xff0c;是用于对小程序进行的切换页面、改变图标、控制是否能被搜索到等的操作第二篇链接、第三篇链接 第四…

QT的TcpServer

Server服务器端 QT版本5.6.1 界面设计 工程文件&#xff1a; 添加 network 模块 头文件引入TcpServer类和TcpSocket&#xff1a;QTcpServer和QTcpSocket #include <QTcpServer> #include <QTcpSocket>创建server对象并实例化&#xff1a; /*h文件中*/QTcpServer…

基于SSM SpringBoot vue宾馆网上预订综合业务服务系统

基于SSM SpringBoot vue宾馆网上预订综合业务服务系统 系统功能 首页 图片轮播 宾馆信息 饮食美食 休闲娱乐 新闻资讯 论坛 留言板 登录注册 个人中心 后台管理 登录注册 个人中心 用户管理 客房登记管理 客房调整管理 休闲娱乐管理 类型信息管理 论坛管理 系统管理 新闻资讯…

Docker-Compose编排LNMP并部署WordPress

前言 随着云计算和容器化技术的快速发展&#xff0c;使用 Docker Compose 编排 LNMP 环境已经成为快速部署 Web 应用程序的一种流行方式。LNMP 环境由 Linux、Nginx、MySQL 和 PHP 组成&#xff0c;为运行 Web 应用提供了稳定的基础。本文将介绍如何通过 Docker Compose 编排 …

BUUCTF---misc---被偷走的文件

1、题目描述 2、下载附件&#xff0c;是一个流量包&#xff0c;拿去wireshark分析&#xff0c;依次点开流量&#xff0c;发现有个流量的内容显示flag.rar 3、接着在kali中分离出压缩包&#xff0c;使用下面命令&#xff0c;将压缩包&#xff0c;分离出放在out3文件夹中 4、在文…

Java -- (part21)

一.File类 1.概述 表示文件或者文件夹的路径抽象表示形式 2.静态成员 static String pathSeparator:路径分隔符:; static String separator:名称分隔符:\ 3.构造方法 File(String parent,String child) File(File parent,String child) Flie(String path) 4.方法 获…

在M1芯片安装鸿蒙闪退解决方法

在M1芯片安装鸿蒙闪退解决方法 前言下载鸿蒙系统安装完成后&#xff0c;在M1 Macos14上打开闪退解决办法接下来就是按照提示一步一步安装。 前言 重新安装macos系统后&#xff0c;再次下载鸿蒙开发软件&#xff0c;竟然发现打不开。 下载鸿蒙系统 下载地址&#xff1a;http…

Android使用kts上传aar到JitPack仓库

Android使用kts上传aar到JitPack 之前做过sdk开发&#xff0c;需要将仓库上传到maven、JitPack或JCenter,但是JCenter已停止维护&#xff0c;本文是讲解上传到JitPack的方式,使用KTS语法&#xff0c;记录使用过程中遇到的一些坑. 1.创建项目(library方式) 由于之前用鸿神的w…
最新文章