news 2026/9/9 7:34:06

SpringBoot3 Swagger UI 打不开?从排查到解决的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot3 Swagger UI 打不开?从排查到解决的完整指南

最近用 SpringBoot3 升级一个老项目时踩了个非常典型的坑:/v3/api-docs这个 OpenAPI 的 JSON 接口访问得干干净净、一点问题没有,可/swagger-ui.html就是打不开,直接 404。换成/swagger-ui/index.html依旧白页,折腾了整整两个晚上才把根因彻底理顺。

这个现象在 Springdoc 2.x + SpringBoot3 这个组合里其实非常常见。网上能搜到的帖子要么只给一个答案,要么就是不区分具体场景,抄过来也没用。这篇文章我直接把自己踩过的坑、每一步排查的思路、以及最终验证可用的完整配置全部摆出来。文章适合正好卡在“api-docs 能访问但 UI 打不开”的朋友,也适合刚准备给 SpringBoot3 项目接 Swagger 文档、想少走弯路的人。

1. 先复现现象,搞清楚“不能访问”到底是哪种不能访问

1.1 我遇到的现场

我的环境是SpringBoot 3.2.x + JDK 17 + springdoc-openapi-starter-webmvc-ui 2.3.0,内嵌 Tomcat 部署。项目启动后所有业务接口正常,但访问文档页时出现了非常分裂的状态:

curl -i http://localhost:8080/v3/api-docs # 200,返回一大段 OpenAPI JSON curl -i http://localhost:8080/swagger-ui.html # 404,Whitelabel Error Page curl -i http://localhost:8080/swagger-ui/index.html # 404,同样打不开

浏览器直接访问/swagger-ui.html,出现的是 Spring Boot 默认的错误页,Tomcat 日志里也没有任何明显的异常堆栈。这个现象最迷惑人的地方在于:既然/v3/api-docs能正常输出,说明 Springdoc 的核心功能已经生效了,为什么 UI 偏偏不行?

这里需要先纠正一个误区:无法访问 UI 不等于 Springdoc 没生效,它只是说明 Swagger UI 这一层没有正常工作。如果你也遇到同样情况,先不要急着怀疑版本,更不要盲目重装依赖,而是要按下面第 2 节的分析,搞清楚 api-docs 和 swagger-ui 在 Spring MVC 里完全就是两条不同的路由。

1.2 常见的三种“HTML 无法访问”表现

根据我后来在网上翻帖子和自己复现的经验,Springdoc 场景下的“HTML 无法访问”基本有三类:

表现典型状态码大概率原因
页面直接 404404依赖缺 UI 包、静态资源映射被改、路径不对
页面返回 403 或被重定向到登录页403 / 302Spring Security 拦截了 swagger-ui 路径
页面返回 200 但白屏或显示一堆 HTML 源码200JS/CSS 加载失败、响应 Content-Type 不对、代理配置问题

这几种情况的原因和解决办法完全不同。你只有先确认自己属于哪一种,再去搜解决方案,才不会像我一样绕远路。

2. 为什么 api-docs 能访问而 html 不行:两类路由的差异

2.1/v3/api-docs走的是 Controller 映射

Springdoc 在启动时会向 Spring 容器注册一个 OpenAPI 相关的@RestController,把/v3/api-docs映射到具体的 Controller 方法上。换句话说,这个地址本质上是一个普通的 MVC 接口,和你自己写的@RestController没有任何区别。

只要 springdoc 的核心 API 库被正确加载,/v3/api-docs就会作为 DispatcherServlet 管理的一个普通端点存在。它不依赖任何静态资源,也不依赖 webjars,所以哪怕 Swagger UI 的资源包完全没有,这个接口依然能正常返回 JSON。这也是为什么很多人看到 api-docs 正常,就不去怀疑依赖问题的原因。

我当时排查时也犯了这个错误,一直在 api-docs 的逻辑里打转,完全没意识到 UI 是另一套资源。

2.2/swagger-ui.html走的是静态资源映射

Swagger UI 本质上是一个纯前端项目,由一堆 HTML、CSS、JavaScript 文件组成。这些文件被打包在 webjars 依赖里,路径通常位于classpath:/META-INF/resources/webjars/swagger-ui/

Springdoc 的自动配置会在启动时注册一个ResourceHandler,把/swagger-ui/**这个 URL 路径映射到上面的 webjars 目录。访问/swagger-ui.html时,Springdoc 还会做一次转发,把它重定向到/swagger-ui/index.html

所以在 Spring MVC 内部,这两条链路是这样的:

路径类型依赖可能被影响的因素
/v3/api-docsController 方法springdoc 核心库DispatcherServlet 是否可用
/swagger-ui.html静态资源映射springdoc UI 库 + webjars 资源静态资源配置、Security、依赖版本、代理

这种差异直接解释了一个关键结论:api-docs 能访问只说明核心库没问题,它无法证明 UI 所依赖的资源包、静态资源映射和访问控制没有问题。

2.3 实际排查时要把两条链路分开看

我后来总结出一个很实用的排查原则:把“API 数据路径”和“UI 资源路径”当成两个独立的问题去看。

如果/v3/api-docs挂了,说明 OpenAPI 数据生成有问题,通常和依赖版本、配置冲突有关。如果发现/v3/api-docs正常但/swagger-ui.html挂了,那就把注意力放到静态资源、安全拦截、反向代理这三件事上。这样排查范围一下子缩小了很多,也避免在错误的层次上浪费时间。

3. 逐个排查:大多逃不过这 5 类原因

3.1 依赖引错或只引了一半

这是最常见的原因,没有之一。很多人用 SpringBoot3 时,只引入了下面这个依赖:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc</artifactId> <version>2.3.0</version> </dependency>

注意:springdoc-openapi-starter-webmvc这个包只包含 OpenAPI 核心能力和自动配置,它不包含 Swagger UI 的前端静态资源。所以它能保证/v3/api-docs正常,但/swagger-ui.html就会 404。

如果你需要 UI 页面,必须引入带ui后缀的完整包:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.3.0</version> </dependency>

另外还要注意几点:

  • 如果是 WebFlux 项目,要引springdoc-openapi-starter-webflux-ui,而不是 webmvc 版本。
  • starter-webmvc-ui这个依赖已经包含了 api 核心能力,不需要额外再引starter-webmvc,否则可能出现重复的 OpenAPI 资源,在某些版本下反而会引发奇怪问题。
  • 检查一下 pom 里是否残留了 Springfox(springfox-swagger2)相关依赖,Springdoc 和 Springfox 同时存在时不一定会直接报错,但争抢路径的情况非常坑。

我在自己项目里最终定位到的原因就是这个:之前只引了starter-webmvc,压根没引入 UI 包。属于那种“看起来简单,但一旦漏掉就怎么调都调不出来”的问题。

3.2 Spring Security 把页面拦截了

如果你项目里引入了spring-boot-starter-security,那依赖没问题也可能打不开 UI。Spring Security 在 SpringBoot3 中默认会拦截所有请求,未认证访问/swagger-ui.html时,通常会被重定向到登录页,或者因为在配置里没放开这些路径而返回 403/404。

SpringBoot3 使用的是 Spring Security 6.x,配置风格已经从旧的WebSecurityConfigurerAdapter换成SecurityFilterChain。如果你在网上搜到的是过时的写法,直接复制会报错。一个标准的放行配置像下面这样:

@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers( "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs.yaml", "/webjars/**" ).permitAll() .anyRequest().authenticated() ); return http.build(); } }

这里几个关键点:

  • requestMatchers而不是旧版的antMatchers,后者在 Spring Security 6 里已经移除。
  • 放行路径不仅要包含/swagger-ui.html,还要放行/swagger-ui/**,因为index.html加载之后还会请求大量 JS/CSS 文件,这些都属于/swagger-ui/**路径。
  • /v3/api-docs/**也要放行。否则可能出现 UI 页面能打开,但页面一直转圈,控制台报“Failed to load API definition”的尴尬情况。
  • 如果你配了 Spring Security 的链路,建议用curl -i看一眼是否有 302 跳转,如果有,基本就是被安全拦截了。

3.3 自定义静态资源配置把 swagger-ui 的路由冲掉

这种情况相对隐蔽。比如有些项目需要在/static/目录下放静态资源,会自定义WebMvcConfigurer

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/**") .addResourceLocations("classpath:/static/"); } }

这段代码本身没太大问题,但如果你注册的 handler 路径过于宽泛,比如/**,在某些情况下会影响 Springdoc 自动注册的/swagger-ui/**映射。

更常见的是在application.yml里做了这种配置:

spring: mvc: static-path-pattern: /static/**

这行配置把 Spring MVC 默认的静态资源匹配模式从原来的/**改成了/static/**。而 Swagger UI 的资源位于 webjars 下,对应的默认映射是/webjars/**。一旦你改了全局静态路径,webjars 的默认映射可能就不生效了,导致/swagger-ui/**的资源全部 404。

解决办法有两个方向:

  1. 尽量不要修改spring.mvc.static-path-pattern,除非你非常清楚自己在做什么。
  2. 如果项目确实需要自定义静态路径,可以在WebMvcConfigurer里显式补上 webjars 的映射:
registry.addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/");

即使全局静态路径被改了,只要补上这一条,/swagger-ui/**所需要的 webjars 资源就能重新被路由到。

3.4 版本不兼容:SpringBoot3 必须用 Springdoc 2.x

这是个坑中坑。SpringBoot2 时代,大家用的 Springdoc 是org.springdoc:springdoc-openapi-ui:1.6.x,这个版本的内部实现基于javax.*命名空间。SpringBoot3 全面迁移到了jakarta.*命名空间,如果你在 SpringBoot3 项目里直接用了 1.x 的 springdoc,很可能启动阶段就报ClassNotFoundExceptionNoClassDefFoundError,甚至静默地导致 UI 不生效。

正确做法是引入 2.x 系列的依赖:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.6.0</version> </dependency>

版本号可以用目前最新的 2.x 稳定版。这里还要提醒一下:检查 pom 时不要只看显示名称,要用mvn dependency:tree | grep springdoc看一下实际解析出来的版本。曾经见过父级 BOM 或者传递依赖把 springdoc 版本覆盖回 1.x 的情况,这种问题在配置里根本看不出来。

3.5 反向代理/Nginx 导致页面资源加载不出来

生产环境普遍会用 Nginx 做反向代理。这种情况下有个典型现象:后端直接访问/swagger-ui.html是好的,但通过 Nginx 域名访问就是白屏,按 F12 看到一堆 JS/CSS 请求 404。

原因多半是 Nginx 的 location 只转发到了根路径,后续浏览器请求/swagger-ui/swagger-ui.css/swagger-ui/index.css这些静态资源时,没有被转发到后端,或者被另一个静态服务拦截了。

一个基础的 Nginx 配置示例:

server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /swagger-ui/ { proxy_pass http://127.0.0.1:8080/swagger-ui/; } location /v3/api-docs { proxy_pass http://127.0.0.1:8080/v3/api-docs; } }

如果后端项目设置了server.servlet.context-path,比如/demo,那 Nginx 的转发也要对应加上前缀,否则/swagger-ui.html访问的链路是通不上的。

还有一类不太容易想到的问题:Nginx 没有配置 MIME 类型表,导致 HTML、CSS、JS 文件返回时Content-Typeapplication/octet-stream。浏览器拿到这种响应后不会按页面渲染,而是直接当成文件下载,或者显示成纯文本源码。这种情况在html 文件无法预览的搜索结果里非常常见。解决办法是在 Nginx 的http块里加上:

include /etc/nginx/mime.types;

加了之后重启 Nginx,再访问 swagger-ui 页面,如果之前的症状是“显示一堆 HTML 标签源码”,这个操作大概率能解决。

3.6 页面返回 200 但一直转圈或加载不出接口列表

还有一种情况:/swagger-ui.html返回 200,页面也出来了,但中间位置一直转圈,控制台提示 “Failed to load API definition”。

这是 Swagger UI 页面启动后,会自己去请求一个 OpenAPI 数据源,默认就是/v3/api-docs。如果这个地址被安全框架拦截、被代理规则挡住、或者因为路径前缀不对导致 404,UI 就会一直卡在加载中。

排查方向:

  • 单独访问一下/v3/api-docs,确认返回的是 200 和 JSON 内容。
  • 如果前面配了 Spring Security,检查/v3/api-docs/**是否放行。
  • 如果项目有 context-path 或网关前缀,检查浏览器地址栏里请求的 api-docs 路径是否正确。

还有一种可能是 Springdoc 配置了自定义urlsconfig-url,指向了一个根本不存在的地址。比如设置springdoc.swagger-ui.config-url: /foo/bar.json,而/foo/bar.json又没有对应的接口,就会导致 UI 加载失败。

4. 直接给一套能用的完整配置

如果你不想逐条排查,可以直接参照这一套配置。这是我目前用的组合,在 SpringBoot3 项目里实测可跑。

4.1 Maven 依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.3.0</version> </dependency>

如果项目里已经有 spring-boot-starter-security,保留即可,不需要为了 swagger 去掉安全框架。

4.2 application.yml 配置

springdoc: api-docs: enabled: true path: /v3/api-docs swagger-ui: enabled: true path: /swagger-ui.html operations-sorter: method tags-sorter: alpha display-request-duration: true

这里几个配置项的含义:

  • springdoc.api-docs.path就是 OpenAPI JSON 的访问路径,默认是/v3/api-docs,可以不写。
  • springdoc.swagger-ui.path是 UI 页面入口地址,默认是/swagger-ui.html。注意这里的 path 虽然看似是 HTML 文件,但实际还是由 Springdoc 转发到/swagger-ui/index.html
  • operations-sortertags-sorter只是 UI 展示层面的排序规则,不写不影响功能。
  • 如果不想让 Swagger UI 自动加载默认的 petstore 示例,可以加上:
springdoc: swagger-ui: disable-swagger-default-url: true

否则打开页面后,右上角可能有一个指向 petstore 的默认下拉项,虽然不影响使用,但第一次看会觉得有点突兀。

4.3 Spring Security 放行配置

如果引入了 Spring Security,参考下面的配置:

@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers( "/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs.yaml", "/webjars/**" ).permitAll() .anyRequest().authenticated() ); return http.build(); } }

如果项目还配置了 OAuth2 资源服务器或自定义过滤器,放行路径要确保在过滤器链中不会被前置过滤器拦截。

4.4 Nginx 反向代理配置

生产环境用 Nginx 时,参考如下:

server { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /swagger-ui/ { proxy_pass http://127.0.0.1:8080/swagger-ui/; } location /v3/api-docs { proxy_pass http://127.0.0.1:8080/v3/api-docs; } }

同时确认http块里引入了mime.types,避免静态资源 Content-Type 错误。

5. 实战排查步骤:三步定位问题

5.1 用 curl 分层判断,别急着开浏览器

遇到这种问题,我强烈建议先用命令行工具做分层判断,而不是直接打开浏览器看。浏览器有缓存、有 JS 执行环境,反而干扰定位。

第一步,确认核心 API 数据是否正常:

curl -I http://localhost:8080/v3/api-docs

如果返回 200,说明 Springdoc 核心链路没问题。

第二步,确认 UI 入口地址的状态:

curl -I http://localhost:8080/swagger-ui.html curl -I http://localhost:8080/swagger-ui/index.html

观察返回的状态码和 Location 头。

第三步,确认静态资源是否可访问:

curl -I http://localhost:8080/swagger-ui/swagger-ui.css

这个请求如果 404,问题基本可以锁定在依赖或静态资源映射上。

5.2 用依赖树检查 Jar 包

mvn dependency:tree | grep springdoc

输出里应该能看到springdoc-openapi-starter-webmvc-ui以及相关的swagger-uiwebjar。如果看到的是springdoc-openapi-ui而且是 1.x 版本,说明依赖引错了;如果只有springdoc-openapi-starter-webmvc而没有带ui的包,说明 UI 资源压根没进类路径。

这个命令也能顺带排查是否同时存在 Springfox 等冲突依赖。

5.3 临时关闭 Security 验证

如果你怀疑是 Spring Security 搞的鬼,最快速的方式是临时排除安全自动配置,验证一下:

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})

改完以后重新启动,再访问/swagger-ui.html。如果此时能正常打开,说明问题就出在安全放行配置上,把配置按第 4.3 节调整即可。如果排除后依然打不开,那问题基本不在安全层,继续查依赖和静态资源映射。

这个操作只能用在本地验证,不要在生产环境直接关闭安全配置。

5.4 检查自动配置是否生效

SpringBoot3 中可以通过启动时加--debug参数来打印自动配置报告:

mvn spring-boot:run -Ddebug

然后在输出里搜索Swaggerspringdoc,看相关的SwaggerUiWebMvcConfigurer是否处于matched状态。如果看到的是negative match,说明某些条件没有满足,顺着提示去看缺少了什么。

这个命令输出的信息量非常大,排查过 Spring Boot 自动配置问题的人应该不陌生,但新手看了可能会觉得太乱。其实只需要关注包含springdocswaggerwebjars的这些片段即可。

6. 常见问题速查表

最后整理一个速查表,方便大家对照自己的现象直接定位,不用从头再读一遍:

现象可能原因解决方式
/v3/api-docs返回 200,/swagger-ui.html返回 404只引了starter-webmvc,没有引 UI 包换成springdoc-openapi-starter-webmvc-ui
/swagger-ui.html返回 403 或跳转登录页Spring Security 拦截放行/swagger-ui/**/v3/api-docs/**/webjars/**
页面 200 但白屏,F12 里 JS/CSS 404静态资源映射被覆盖或代理未转发/swagger-ui/**检查static-path-pattern;补 Nginx location
页面显示 HTML 源码Nginx 未加载mime.typeshttp块里include mime.types;
启动报javax.servlet相关错误Springdoc 1.x 与 SpringBoot3 不兼容升级到 Springdoc 2.x
页面能打开但一直转圈,控制台报 API definition 加载失败/v3/api-docs被安全拦截或路径不对放行 api-docs,检查 context-path 和代理前缀
/swagger-ui.html跳转/swagger-ui/index.html后还是 404webjars 资源缺失或浏览器缓存强刷缓存,检查依赖树,直接访问/swagger-ui/index.html

7. 总结一下我的排查顺序

这个问题真正难的地方不是解决方案本身,而是它背后的故障现象会诱导你在错误的方向上反复试探。我现在遇到类似问题,会严格按这个顺序来:

第一步,用curl/v3/api-docs/swagger-ui.html的状态码,先确认到底是哪一层挂了。第二步,跑mvn dependency:tree | grep springdoc,确认 UI jar 是否真的在依赖里。第三步,如果涉及安全框架,临时 exclude 掉 Security 自动配置做验证。第四步,如果以上都正常,再考虑代理、路径前缀、Content-Type 这些外部因素。

另外还有一个小建议:在项目里显式配置springdoc.api-docs.enabledspringdoc.swagger-ui.enabled,不要完全依赖默认值。这样后续排查时,至少能确定配置开关没有被隐式改掉。如果你接口数量多,还可以配合springdoc.group-configs做接口分组,Swagger UI 左上角会多一个分组下拉框,文档管理体验会好很多。

希望这篇踩坑记录能帮你少熬一个晚上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 7:32:25

STM32C5轮询读取LSM6DSOW陀螺仪数据:从寄存器到实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 7:32:16

Java参数校验库选型:Apache Commons Validator与ValidX全面对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 7:31:01

STM32C5通过SPI读取LSM6DSVE陀螺仪数据详解与调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 7:29:59

国产GPU实测:算力租赁视角下的性能、成本与生态真相

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/9 7:19:44

HUB12接口16x32 LED点阵屏驱动:从扫描原理到STM32实现

简介&#xff1a;一套基于51单片机的点阵屏控制程序源码&#xff0c;面向嵌入式入门学习者&#xff0c;适合课程设计、电子竞赛或LED点阵显示模块开发。程序围绕HUB12单屏接口、1632分辨率&#xff08;512个LED点&#xff09;设计&#xff0c;覆盖51单片机IO口初始化、HUB12接口…

作者头像 李华
网站建设 2026/9/9 7:19:31

嵌入式面试核心考点与避坑指南:从C语言到Linux驱动全解析

嵌入式面试准备了三个月&#xff0c;面了十几家&#xff0c;从刚开始被问得额头冒汗&#xff0c;到后面基本能猜到面试官下一句要问什么&#xff0c;这个过程中我对“嵌入式岗位到底想招什么人”这件事的理解完全变了。今天不聊空话&#xff0c;直接把我踩过的坑、整理过的题、…

作者头像 李华