Spring Cloud Gateway聚合下游服务的Swagger UI或OpenAPI文档时,文档页面无法展示的表现有很多种:打开后一片空白、页面框架出现但接口列表一直加载、提示No API definition provided、静态资源返回404或401。排查这类问题不能只盯着某一个配置,因为从浏览器到最终拿到接口文档,至少要经过静态页面、样式脚本和JSON描述三个环节,任何一个环节被网关路由、前缀裁剪或安全策略切断,都会表现为展示失败。

把请求链路拆开看会更直观。浏览器先请求网关暴露的文档入口,比如/swagger-ui.html或/webjars/swagger-ui/index.html;页面加载后会继续向网关请求/v3/api-docs或/swagger-config,再由网关转发到下游服务。因此建议先在下游服务单机访问这些地址,如果下游正常而网关异常,就集中检查网关的路由、过滤器和安全配置。
一、先核对路由匹配与StripPrefix是否合理
网关配置中经常会为每个服务的文档挂一个前缀,例如用户服务的所有请求都走/user-api/**。如果路由配置为Path=/user-api/**且没有做前缀处理,那么访问/user-api/v3/api-docs时,下游服务收到的仍然是/user-api/v3/api-docs。但下游SpringDoc通常认为文档地址是/v3/api-docs,于是返回404。
这个问题可以通过StripPrefix=1解决,它会把第一个路径段去掉再转发。下面是配置示例:
spring:
cloud:
gateway:
routes:
- id: user-service-docs
uri: http://localhost:8081
predicates:
- Path=/user-api/**
filters:
- StripPrefix=1
修改后,网关访问/user-api/v3/api-docs,下游实际收到/v3/api-docs。但是要特别注意路由顺序:如果网关中存在Path=/**这类宽泛路由,并且排在文档路由前面,请求会被它先拦截。Spring Cloud Gateway按照配置顺序匹配,命中后不再继续。因此应把/swagger-ui/**、/webjars/**、/v3/api-docs/**等文档路由放在更靠前的位置,或者至少放在兜底路由之前。
另外,有些项目只转发/v3/api-docs,却忘了转发/swagger-ui.html和/webjars/**,导致页面能出现框架但不加载脚本或接口。建议为文档静态资源单独配置一条路由,或在下游服务中统一包含这些资源后再由网关统一转发。
二、检查WebFlux安全链是否放行文档路径
网关工程如果引入了Spring Security,默认会保护所有路由。即使路由转发配置正确,浏览器请求/webjars/swagger-ui/swagger-ui.css,也可能因为未登录被重定向到登录页或直接返回401。因为在网络安全配置中,静态文件和接口文档并没有被自动放行,需要显式声明。
在WebFlux网关中,安全配置通常使用SecurityWebFilterChain。下面是一个允许匿名访问文档路径的配置:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(exchange -> exchange
.pathMatchers("/swagger-ui/**", "/webjars/**", "/v3/api-docs/**", "/swagger-resources/**")
.permitAll()
.anyExchange()
.authenticated())
.oauth2Login();
return http.build();
}
}
要注意路径匹配符是否符合当前Spring Security版本。旧版使用ServerHttpSecurity.AuthorizeExchangeSpec的pathMatchers,新版写法差别不大,关键是把文档相关路径完整列出来。/swagger-resources/**和/swagger-config这类资源经常被遗漏,一旦漏掉,页面打开后JS请求配置失败,仍然无法显示接口。
除了Spring Security,如果网关前面还有Nginx或API管理平台,也要确认这些组件不会对/swagger-ui和/v3/api-docs做额外认证拦截。排查时可以直接用curl请求网关地址,观察返回状态码和响应头,判断是网关层拒绝还是下游拒绝。
三、跨域配置与接口数据加载问题
Swagger UI加载接口列表时,通常会从网关域名请求/v3/api-docs,这与页面本身同源,理论上不涉及跨域。但如果聚合配置中把url写成了下游服务的完整地址,比如http://localhost:8081/v3/api-docs,浏览器就会直接跨域请求下游服务。一旦下游没有允许网关注册的源,或请求带上了额外的自定义头,浏览器会先发送OPTIONS预检,预检失败则接口列表为空。
更稳妥的做法是,聚合文档时使用网关自身的相对路径。在application.yml中可以这样配置:
springdoc:
swagger-ui:
urls:
- name: user-service
url: /user-api/v3/api-docs
- name: order-service
url: /order-api/v3/api-docs
这里/user-api/v3/api-docs会经过网关转发,不受浏览器跨域限制。若确实需要支持跨域访问,可以在网关层配置全局CORS:
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origin-patterns: 'http://localhost:*'
allowed-methods:
- GET
- OPTIONS
allowed-headers: '*'
allow-credentials: true
网关配置CORS时有一个容易忽略的点:如果下游服务自己也设置了CORS响应头,网关层可能会重复添加,导致浏览器端出现Access-Control-Allow-Origin包含多个值的错误。建议只在网关上统一处理,下游服务的CORS配置可以关闭或保持一致。
四、聚合展示时的版本与路径重写问题
SpringDoc存在1.x和2.x两代版本,配置项和行为存在差异。Spring Boot 2.x通常使用SpringDoc 1.x,Spring Boot 3.x使用SpringDoc 2.x。如果从旧项目迁移到新版Spring Cloud Gateway,原来的springdoc.swagger-ui.urls可能仍然可用,但在某些版本中,静态资源路径变成了/v3/api-docs/swagger-config。因此升级后文档展示不出来,可以先查看页面请求的接口地址是否与网关路由一致。
如果需要把不同服务的文档聚合到同一个Swagger UI入口,还可以让网关提供默认的swagger-config请求,返回多个服务文档地址。这个可以在网关层添加一个简单的处理接口,或者使用SpringDoc的网关聚合功能。无论哪种方式,返回的URL都应该是从浏览器可达的地址,而不是后端服务内部地址。
当路径经过多次重写时,建议给每个服务的前缀保持固定规则,比如/api/user-service/**。配合RewritePath或StripPrefix使用。下面是一个使用RewritePath的配置,注意正则中的命名捕获组写法:
spring:
cloud:
gateway:
routes:
- id: order-service-docs
uri: http://localhost:8082
predicates:
- Path=/order-api/**
filters:
- RewritePath=/order-api/(?<segment>.*), /$\{segment}
这个配置把/order-api/v3/api-docs重写为/v3/api-docs,同时保留后续路径。要注意YAML中$\{segment}的转义写法,避免被Spring当占位符提前解析。
五、用日志和简单过滤器快速定位故障段
遇到复杂路由时,猜测往往不如直接看请求路径。可以在application.yml中临时打开网关日志:
logging:
level:
org.springframework.cloud.gateway: DEBUG
org.springframework.web: DEBUG
日志中会显示路由匹配结果和转发地址。如果仍然不够直观,可以添加一个打印请求路径的全局过滤器:
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class DocRouteLogFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
System.out.println("Gateway request path: " + path);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -100;
}
}
启动网关后访问文档页面,观察控制台输出的路径序列。正常情况下,你应当先看到/swagger-ui.html或对应页面路径,然后出现/webjars/...和/v3/api-docs。如果只有页面路径而没有后续请求,通常说明页面被安全策略拦截或返回了错误内容;如果路径完整但下游报404,则要检查路由是否把错误的前缀传给了服务。
对照日志可以快速把故障限定在静态资源链路还是接口描述链路。多数配置错误都能在修改路由顺序、补全StripPrefix和放行路径后恢复。
Spring Cloud GatewayAPI文档故障排除修改时间:2026-09-19 08:19:06