SpringCloudGateway集成SpringDoc CORS问题

2023-11-07

SpringCloudGateway集成SpringDoc CORS问题

集成SpringDoc后,在gateway在线文档界面,请求具体的服务接口,报CORS问题

Failed to fetch.
Possible Reasons:
CORS
Network Failure
URL scheme must be “http” or “https” for CORS request.

分析

其实是网关直接请求具体服务/v3/api-docs接口(默认),获取文档数据,里面包含该服务注册上来的地址,gateway swagger-ui解析该接口数据,根据里面的地址直接请求。可是网关地址,跟具体的服务地址肯定不同源,在gateway集成界面请求,肯定报跨的问题。
或者是该微服务群,只能通过网关访问,直接请求具体的服务地址,网络不通。

{
	"openapi": "3.0.1",
	"info": {
		"title": "XX服务",
		"description": "XX服务开发接口文档",
		"version": "1.0.0"
	},
	"servers": [
		{
			"url": "http://[2408:8456:601:9f52:225d:8f68:5e14:6ff4]:8201",
			"description": "Generated server url"
		}
	]
}

思路

通过在gateway编写全局GatewayFilter,拦截集成时请求的 /xx/v3/api-docs接口(默认)接口,修改返回的数据,在servers写入通过网关直接访问的地址,就能解决在线文档请求接口,存在的跨域问题和网络不通问题。

实现

/**
 * 处理服务 /v3/api-docs接口返回的数据,在servers里添加可以通过网关直接访问的地址
 */
@Slf4j
@Component
@ConditionalOnProperty(name = SPRINGDOC_ENABLED, matchIfMissing = true)
public class DocServiceUrlModifyGatewayFilter implements GlobalFilter, Ordered {
    @Autowired
    private SpringDocConfigProperties springDocConfigProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //直接用配置类的值,默认值是 /v3/api-docs
        String apiPath = springDocConfigProperties.getApiDocs().getPath();
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        URI uri = request.getURI();

        //非正常状态跳过
        if (response.getStatusCode().value() != HttpStatus.OK.value()) {
            return chain.filter(exchange);
        }

        //非springdoc文档不拦截
        if (!(StringUtils.isNotBlank(uri.getPath()) && uri.getPath().endsWith(apiPath))) {
            return chain.filter(exchange);
        }

        String uriString = uri.toString();
        String gatewayUrl = uriString.substring(0, uriString.lastIndexOf(apiPath));
        DataBufferFactory bufferFactory = response.bufferFactory();


        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                //不处理
                if (!(body instanceof Flux)) {
                    return super.writeWith(body);
                }

                //处理SpringDoc-OpenAPI
                Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                    DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                    DataBuffer join = dataBufferFactory.join(dataBuffers);
                    byte[] content = new byte[join.readableByteCount()];
                    join.read(content);
                    DataBufferUtils.release(join);
                    try {
                        // 流转为字符串
                        String responseData = new String(content, StandardCharsets.UTF_8);
                        Map<String, Object> map = JsonUtils.json2Object(responseData, Map.class);
                        //处理返回的数据
                        Object serversObject = map.get("servers");
                        if (null != serversObject) {
                            List<Map<String, Object>> servers = (List<Map<String, Object>>) serversObject;
                            Map<String, Object> gatewayServer = new HashMap<>();
                            //网关地址
                            gatewayServer.put("url", gatewayUrl);
                            gatewayServer.put("description", "Gateway server url");
                            //添加到第1个
                            servers.add(0, gatewayServer);
                            map.put("servers", servers);
                            responseData = JsonUtils.object2Json(map);
                            byte[] uppedContent = responseData.getBytes(StandardCharsets.UTF_8);
                            response.getHeaders().setContentLength(uppedContent.length);
                            return bufferFactory.wrap(uppedContent);
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }

                    return bufferFactory.wrap(content);
                }));
            }
        };
        // replace response with decorator
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

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


    class JsonUtils {

        public static final ObjectMapper OBJECT_MAPPER = createObjectMapper();

        /**
         * 初始化ObjectMapper
         *
         * @return
         */
        private static ObjectMapper createObjectMapper() {

            ObjectMapper objectMapper = new ObjectMapper();

            objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
            objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);

            objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);

//        objectMapper.registerModule(new Hibernate4Module().enable(Hibernate4Module.Feature.FORCE_LAZY_LOADING));
            objectMapper.registerModule(new JavaTimeModule());

            return objectMapper;
        }

        public static String object2Json(Object o) {
            StringWriter sw = new StringWriter();
            JsonGenerator gen = null;
            try {
                gen = new JsonFactory().createGenerator(sw);
                OBJECT_MAPPER.writeValue(gen, o);
            } catch (IOException e) {
                throw new RuntimeException("不能序列化对象为Json", e);
            } finally {
                if (null != gen) {
                    try {
                        gen.close();
                    } catch (IOException e) {
                        throw new RuntimeException("不能序列化对象为Json", e);
                    }
                }
            }
            return sw.toString();
        }


        /**
         * 将 json 字段串转换为 对象.
         *
         * @param json  字符串
         * @param clazz 需要转换为的类
         * @return
         */
        public static <T> T json2Object(String json, Class<T> clazz) {
            try {
                return OBJECT_MAPPER.readValue(json, clazz);
            } catch (IOException e) {
                throw new RuntimeException("将 Json 转换为对象时异常,数据是:" + json, e);
            }
        }


    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringCloudGateway集成SpringDoc CORS问题 的相关文章

随机推荐

  • Python中的Lambda函数:简洁而强大的匿名函数

    引言 Python是一种功能强大且易于学习的编程语言 它提供了许多方便的语法和功能 以简化代码的编写和理解 其中之一就是Lambda函数 Lambda函数是Python中的一种匿名函数 它使得在需要函数对象的任何地方都能使用函数表达式 本文
  • 嵌入式系统开发项目管理

    项目生命周期五大阶段 1 项目启动阶段 1 项目可行性分析 一个成功的产品 应该从以下3个方面来观察评估 设计产品 商业行为 产品设计前 要做好市场调查和评估 要考虑产品的时效性 市场需求和技术可行性 产品设计结束后要写下详细的产品规格 技
  • L2TP详解(一)

    今天继续给大家介绍HCIE安全 本文给大家介绍的是L2TP相关内容 包括L2TP的特点和应用场景 一 L2TP简介 L2TP是一种二层的VPN技术 它提供了对PPP链路层数据帧的隧道传输支持 允许二层链路端点和PPP会话驻留在不同设备上 扩
  • 论文笔记:Constructing spatiotemporal speed contour diagrams: usingrectangular or non-rectangular paralle

    Constructing spatiotemporal speed contour diagrams using rectangular or non rectangular parallelogram cells 2019 Transpo
  • c++求模运算的应用

    在 C C 中 符号为求模运算符 即 a b 表示 a 除以 b 的余数 周期问题 我们在奥数中经常遇到这样的问题 给你一串数字 4 5 6 4 5 6 4 5 6 求这里的第 1000 个数字是几 我们发现这是一个周期数列 周期为 3 a
  • 华为OD机试真题 Java 实现【获取字符串中连续出现次数第k多的字母的次数】【2023Q1 100分】,附详细解题思路

    一 题目描述 给定一个字符串 只包含大写字母 求在包含同一字母的子串中 长度第 k 长的子串的长度 相同字母只取最长的那个子串 二 输入描述 第一行有一个子串 1 lt 长度 lt 100 只包含大写字母 第二行为 k的值 三 输出描述 输
  • 六款常用的linux C/C++ IDE

    摘要 一 AnjutaAnjuta是一个多语言的IDE 它最大的特色是灵活 同时打开多个文件 内嵌代码级的调试器 调用gdb 应用程序向导 Application wizards 可以方便的帮助你创建GNOME程序而不需要你自己写一些与你兴
  • 迁移MongoDB数据库及数据(MongoDB数据库的备份与恢复)

    备份 在有数据的地方使用mongodump h connection d 数据库名 o 保存的文件夹 比如 mongodump h 127 0 0 1 27017 d my db o my back dir 恢复 在想要覆盖的地方使用mon
  • windows环境中使用goland构建linux二进制文件并运行

    大家都知道 go语言可打包成目标平台二进制文件是它的一大优势 如此一来 go项目在服务器不需要配置go环境和依赖就可以运行 操作方式 需求 在windows环境中打包部署golang项目到Centos 7 运行 打包环境 windows 1
  • makefile常用编译选项

    我们习惯创建一个环境变量文件Inc mk来定义常用的变量 CC gcc CXX g std c 11 AR ar ARFLAGS scurv RANLIB ranlib CFLAGS CXXFLAGS INCLUDE LDFLAGS CFL
  • Datagrip如何访问集成Kerberos协议的Hive数据库

    Datagrip如何访问集成Kerberos协议的Hive数据库 简介 环境说明 kerberos秘钥 Datagrip配置 环境变量 安全服务文件 参考连接 简介 背景说明 hive数据库默认不需要配置用户名密码 基于 Datagrip
  • Paper writting accumulation

    Abstract last Our extensive experiments on multiple benchmark datasets demonstrate the superiority of compared to a numb
  • DVWA - XSS DOM (low)

    low级别 XSS 全称Cross Site Scripting 即跨站脚本攻击 某种意义上也是一种注入攻击 是指攻击者在页面中注入恶意的脚本代码 当受害者访问该页面时 恶意代码会在其浏览器上执行 需要强调的是 XSS不仅仅限于JavaSc
  • 抖音 x-gorgon 03 免费生成接口 抖音6.3.0版本

    接口已经更新 请参考文章 https blog csdn net wql2014302721 article details 113737772 相关文章 抖音爬虫从0到1 第一弹 环境配置 抖音 x gorgon 03 免费生成接口 抖音
  • Markdown文档小技巧

    MarkDown文档现在可以说普及率原来越高 相对于其他的编辑 记得重点是如何输出内容 提供给你对应的 神秘代码 来完成一些如生成目录 数字符号 链接啊这些东西 更加关心文档的内容 0 推荐的的markdown文档编辑工具 我这里推荐Typ
  • android 关于mk如何引用其它so库

    通常 开发android的软件 常常需要编译so库 然编译的这个so库 需要调用多个其它的so库 这里主要记录一下 编译so库时 调用其它so库的mk 是怎么写的 这里讲的示例 需要编译出libtest3 so 它需要调用libtest1
  • Qt:QML:程序最大化最小化全屏之间的切换

    啥也不说了 直接上代码 import QtQuick 2 5 import QtQuick Controls 1 4 import QtQuick Window 2 0 ApplicationWindow id idMainWindow v
  • 美的年营收3412亿:净利286亿 何享健控制31.5%股权

    雷递网 雷建平 5月4日报道 美的日前公布年报 年报显示 美的2021年营收为3412亿元 较上年同期的2842亿元增长20 06 美的2021年净利为285 74亿元 较上年同期的272 23亿元增长4 96 扣非后净利为259 29亿元
  • oracle数据库中varchar类型字段中存放的有数字和汉字情况,比较大小问题解决

    如果你也正在为这种问题头疼 当你看到我这篇博客时 恭喜你 问题的解决方案来了 你头疼的问题可以解决了 这个问题是我在最近的项目中遇到的难题 上网查阅了很多资料 用什么平常使用的 lt lt gt gt 都没能解决问题 最后还是问了大牛 才解
  • SpringCloudGateway集成SpringDoc CORS问题

    SpringCloudGateway集成SpringDoc CORS问题 集成SpringDoc后 在gateway在线文档界面 请求具体的服务接口 报CORS问题 Failed to fetch Possible Reasons CORS