SpringCloudGateway集成SpringDoc CORS问题

文章讲述了在SpringCloudGateway集成SpringDoc后,由于跨域策略导致的CORS问题,通过全局GatewayFilter拦截并修改/v3/api-docs接口数据,将服务地址替换为网关地址,从而解决文档访问的跨域和网络不通问题。

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);
            }
        }


    }
}
在Spring Cloud 2.6版本下将GatewaySpringdoc进行整合,可以按照以下步骤操作: ### 1. 添加依赖 在`pom.xml`文件中添加Springdoc和Spring Cloud Gateway相关依赖: ```xml <dependencies> <!-- Spring Cloud Gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Springdoc OpenAPI for Spring WebFlux (适用于Gateway) --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-webflux-ui</artifactId> <version>1.6.14</version> <!-- 选择合适的版本 --> </dependency> </dependencies> ``` ### 2. 配置Springdoc 在`application.yml`或`application.properties`中进行相关配置: ```yaml springdoc: api-docs: enabled: true swagger-ui: path: /swagger-ui.html group-configs: - group: gateway paths-to-match: /api/** # 根据实际情况配置匹配路径 ``` ### 3. 配置Spring Cloud Gateway 创建Gateway配置类,配置路由规则: ```java import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class GatewayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route("service1_route", r -> r.path("/api/service1/**") .uri("lb://service1")) .route("service2_route", r -> r.path("/api/service2/**") .uri("lb://service2")) .build(); } } ``` ### 4. 处理跨域问题(可选) 如果存在跨域请求问题,可以添加以下配置: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.reactive.CorsWebFilter; import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.cors.CorsConfiguration; import java.util.Arrays; @Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration corsConfig = new CorsConfiguration(); corsConfig.setAllowedOrigins(Arrays.asList("*")); corsConfig.setMaxAge(3600L); corsConfig.addAllowedMethod("*"); corsConfig.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfig); return new CorsWebFilter(source); } } ``` ### 5. 验证整合 启动应用程序,访问`http://localhost:port/swagger-ui.html`,应该可以看到Swagger UI界面,并且可以查看各个服务的API文档。 ### 示例代码解释 - **依赖添加**:引入Spring Cloud GatewaySpringdoc OpenAPI相关依赖,确保项目可以使用Gateway和生成API文档。 - **配置文件**:配置Springdoc的API文档和Swagger UI路径,以及分组信息。 - **Gateway配置**:使用`RouteLocator`配置路由规则,将请求转发到不同的服务。 - **跨域配置**:添加跨域过滤器,允许所有来源的请求。 通过以上步骤,就可以在Spring Cloud 2.6版本下将GatewaySpringdoc进行整合。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值