ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unable to infer base url:Swagger3 拦截器放行

Unable to infer base url:Swagger3 拦截器放行 1. 报错现场先把 Unable to infer base url 这个坑看清楚只要你在 Spring Boot 项目里集成过 Swagger3springdoc-openapi并且加了拦截器大概率会撞上这个页面浏览器打开swagger-ui.html然后页面一片空白控制台飘红提示Unable to infer base url或者Unable to render this definition。我第一次遇到的时候也懵了几分钟因为本地开发时一切正常代码没动过只是加了一个登录校验的拦截器Swagger 页面就废了。这两个报错在中文社区里被问得非常多核心关键词无非就是 Swagger3、拦截器、Unable to infer base url 这几个说明这是一个几乎人人都要踩一次的经典坑。先把结论摆在前面这两个报错本身不是 Swagger 的 bug也不是 springdoc 版本问题而是 Swagger UI 在加载过程中发出的某几个请求被你的拦截器挡下来了返回的内容不是它期望的 JSON于是它自己解析失败吐出了这两句提示。所以解决思路非常明确——把 Swagger3 依赖的那几个端点从拦截器里放出去。但真正动手时坑又不止一个路径写错、版本用错、放行方式不对、多拦截器顺序乱掉任何一个环节出问题页面照样白。这篇文章我会按现象—根因—修复—排查的顺序把整个链路拆开讲。适合刚接触 Swagger3 的同学也适合维护老项目、正在做 springdoc 升级的同行。读完你应该能自己定位到底是哪一层拦截把请求拦掉了而不用到处试。1.1 两个报错分别在说什么Unable to infer base url这句话的官方语境是Swagger UI 试图推断当前 API 文档的 base url但是失败了。Swagger UI 加载时会先请求一个配置文件拿到配置后才知道去哪里取 OpenAPI 描述。如果这个配置文件请求被拦截返回了 HTML 登录页、302 跳转或者空白内容Swagger UI 就无法从中推断出 base url于是报这句话。它本质上是一个拿不到配置的报错而不是配置内容不对。Unable to render this definition则更进一步它已经拿到了某个响应但尝试把这个响应渲染成 API 定义时失败了。常见的原因是这个响应压根不是合法的 OpenAPI JSON比如你的拦截器给所有未登录请求统一返回了{code:401,msg:未登录}这种包装体。Swagger UI 解析这个 JSON找不到openapi、paths这些字段于是无法渲染。还有一种情况是响应类型是text/htmlSwagger UI 把它当 JSON 解析直接抛错。两者经常同时出现有时候只出现一个。区别在于拦截发生在链路的哪一步拦在取配置这一步通常是第一个报错拦在取 api-docs这一步通常是第二个。搞清这个区别排查的时候能省不少时间。1.2 Swagger3 页面加载背后跑了哪些请求很多人以为打开一个 HTML 页面就完事了其实 Swagger UI 在浏览器侧的加载链路有好几步。第一步你访问/swagger-ui.htmlspringdoc 会 302 重定向到/swagger-ui/index.html。第二步index.html里加载了 Swagger UI 的静态资源这些资源默认通过/swagger-ui/**暴露。第三步页面 JS 发起请求/v3/api-docs/swagger-config获取配置信息这里面包含了 api-docs 的地址。第四步再按配置去请求/v3/api-docs拿到完整的 OpenAPI JSON。第五步渲染成我们看到的接口列表。这条链路里任何一步被拦截器挡住都会出现上面两种报错之一。尤其是第三步和第四步因为它们返回的是 JSON而你项目里的统一响应拦截、登录校验拦截最容易顺手把它们也统一处理了。理解了完整链路你就能明白为什么单放行一个路径往往不够——要放行的是一整组路径。提示排查时打开浏览器 F12 的 Network 面板过滤api-docs看这些请求的响应到底是什么。是 302、是 401 的 JSON、还是 HTML 登录页一眼就能定位拦截发生在哪一步。2. 根因拆解拦截器是怎么把 Swagger 拦死的知道了现象接下来要把为什么讲透。拦截器本身没有错它就是干拦截这件事的问题出在你默认拦截了所有路径而 Swagger3 需要的路径没有被显式排除。这一节我把三种最常见的拦截方式逐一拆开看看它们分别在 Swagger 请求上做了什么导致最终报错。你项目里可能用了 HandlerInterceptor 做登录校验也可能用了 Spring Security 的过滤器链甚至可能有多个拦截器层层叠加。这三种情况的表现略有不同但归因逻辑是一样的请求在到达 springdoc 的控制器之前就被改变了。理解这个改变的具体形态是解决问题的关键。2.1 拦截器拦截后到底返回了什么假设你的拦截器逻辑长这样拦截到请求后检查 Token没有就response.sendRedirect(/login)或者直接把{code:401}写回去。那么当 Swagger UI 请求/v3/api-docs/swagger-config时它收到的要么是 302 到登录页要么是一段 401 的 JSON。无论是哪一种Swagger UI 都拿不到它想要的配置对象。302 的情况下浏览器会跟着跳转到登录页最终 Swagger UI 拿到的是一整页 HTML。它把 HTML 当 JSON 解析解析失败于是Unable to infer base url。直接返回 401 JSON 的情况下它拿到了 JSON但里面没有它要的urls字段于是同样推断不出 base url。这就是为什么放行必须做而不是指望 Swagger 能聪明地跳过。还有一种更隐蔽的情况你的拦截器对请求做了响应包装把 springdoc 返回的 JSON 又包了一层。比如所有响应都被 AOP 或 ResponseBodyAdvice 改成了{code:0,data:{...}}。这种情况下 Swagger UI 收到的是被嵌套的 JSON结构对不上同样渲染失败。2.2 从 Swagger2 到 Swagger3 的路径迁移陷阱这是踩坑率最高的一个点。很多老项目原来用的是 Swagger2springfox放行路径写的是/swagger-ui.html、/swagger-resources/**、/v2/api-docs、/webjars/**。后来升级到 Swagger3springdoc代码换了依赖但拦截器里的放行路径没跟着改于是新路径/v3/api-docs/**和/v3/api-docs/swagger-config没有被放行页面就废了。我整理了一张对照表升级的时候照着改就行用途Swagger2 (springfox)Swagger3 (springdoc)UI 入口/swagger-ui.html/swagger-ui.html 或 /swagger-ui/index.htmlUI 静态资源/webjars/**/swagger-ui/**文档 JSON/v2/api-docs/v3/api-docs配置/swagger-resources/**/v3/api-docs/swagger-config注意新版 UI 资源走的是/swagger-ui/**而不是/webjars/**这个变化最容易被忽略。如果你只放行了 api-docs 而没放行 swagger-ui 静态资源页面可能加载不出来样式或者部分资源 404。另外Spring Boot 2.6 之后默认的路径匹配策略从AntPathMatcher换成了PathPatternParser而 Spring Boot 3.x 完全采用 PathPatternParser。这会影响通配符写法。通常/**两种策略都支持但如果你写了/swagger-ui/*这种单星在 PathPatternParser 下只匹配一级路径/swagger-ui/index.html就匹配不上了。这种细节非常隐蔽值得留意。2.3 为什么鉴权类拦截器最容易踩这个坑在真实的项目里出问题的往往不是随便一个拦截器而是那个做登录鉴权、权限校验的全局拦截器。原因很简单它的默认策略是拦天下也就是除了登录相关接口其他一律要 Token。对于这种拦截器开发同学在写放行白名单时通常只列了登录、注册、验证码这几个明显的接口完全没想到 Swagger 的文档端点也要放行。开发环境可能因为拦截器配置了 profile 或者本地开了 debug 开关没生效所以本地看着正常一旦部署到测试环境或生产环境拦截器正式启用Swagger 立刻挂掉。这也是本地好好的、一上线就白屏的典型原因。所以正确的做法是凡是做全局鉴权的拦截器白名单里必须固定包含 Swagger3 的这组路径把它当作基础配置的一部分而不是临时补丁。下面章节就来讲具体怎么放。3. 动手修三种放行方案与选择建议根治的方向就是放行但放行的写法有好几种适用场景不同。有人喜欢在拦截器内部判断路径有人喜欢在注册拦截器时用excludePathPatterns还有人干脆用 Spring Security 的过滤链配置。我逐个讲清楚并给出我自己的推荐。这里强调一个原则放行的路径要精确且完整。精确是指不要图省事直接放行/那等于把整个鉴权关了完整是指这组路径一个都不能少否则会时好时坏让你反复排查。3.1 方案A在拦截器里按路径排除如果你的拦截器是标准 HandlerInterceptor最直接的做法是在preHandle里判断请求路径命中 Swagger 相关路径就直接return true。这种方式的好处是逻辑集中坏处是路径判断容易写散。代码大概长这样public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri request.getRequestURI(); // Swagger3 端点直接放行 if (uri.contains(/v3/api-docs) || uri.contains(/swagger-ui) || uri.contains(/swagger-resources) || uri.contains(/webjars) || uri.contains(/doc.html)) { return true; } // 其余鉴权逻辑 // ... return true; }用contains而不是equals是为了兼容 context-path 的情况。比如你的应用配了server.servlet.context-path/api那真实 URI 是/api/v3/api-docs用contains就不会漏。但这种写法的隐患在于路径列表变多以后会越来越乱而且分散在每个拦截器里。如果你有多个拦截器每个都要抄一遍很容易漏。3.2 方案B抽到统一配置类里维护排除规则我更推荐把放行逻辑交给WebMvcConfigurer的excludePathPatterns这样路径规则集中在一处拦截器本身保持干净Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/**) .excludePathPatterns( /swagger-ui/**, /swagger-ui.html, /v3/api-docs/**, /swagger-resources/**, /webjars/**, /doc.html, /error ); } }用这种方式拦截器根本不会对 Swagger 请求执行也就没有返回了什么的问题。这就是我平时最常用的写法配置一眼能看全。需要额外提醒的是/error也要排除不然 Swagger 内部出错时错误页会被鉴权拦一下报错信息会变得莫名其妙。3.3 方案C多个拦截器叠加时的顺序与覆盖面复杂项目里往往不止一个拦截器可能有日志拦截器、签名校验拦截器、登录拦截器。excludePathPatterns只对注册了排除规则的那个拦截器生效其他拦截器如果没有同样排除照样会拦。这是很多人改完还是报错的原因。举个例子你给登录拦截器加了排除但签名拦截器没加它依然会对/v3/api-docs做签名校验返回签名失败Swagger 照样白。所以多拦截器场景下要么每个拦截器的排除列表都补齐要么用order明确顺序并让基础拦截器统一放行 Swagger。我的经验是把哪些路径对所有拦截器都放行这件事做成一个常量类多个excludePathPatterns都引用它避免各写各的漏掉。比如public final class InterceptorWhiteList { public static final String[] SWAGGER { /swagger-ui/**, /swagger-ui.html, /v3/api-docs/**, /swagger-resources/**, /webjars/**, /doc.html, /error }; }然后在每个拦截器注册时统一excludePathPatterns(InterceptorWhiteList.SWAGGER)。这样即使以后加了新拦截器只要引用这个常量就不会漏。注意如果你用的是 knife4jdoc.html那个入口它底层也是基于 springdoc依赖的同样包括/v3/api-docs/**和/swagger-resources/**放行规则要一视同仁不能只放doc.html。4. 版本适配springdoc 依赖与 Spring Boot 的搭配路径对了、放行也加了还是打不开那就有可能是依赖或版本没对上。Spring Boot 2.x 和 3.x 在 springdoc 的坐标、包名、路径上都有差异用错了会直接编译不过或者运行期报错。这一节把选型的细节讲清楚。我见过不少人搜到一份教程就直接抄结果 Spring Boot 3.x 的项目里贴了 1.x 的依赖启动报类找不到。这类问题排查起来很费劲因为报错信息和 Swagger 拦截的报错混在一起容易误判方向。4.1 依赖坐标别选错Spring Boot 2.x 项目用springdoc-openapi-uiSpring Boot 3.x 项目必须用springdoc-openapi-starter-webmvc-ui。这不是可有可无的差别Spring Boot 3 基于 Jakarta EE 9包名从javax.*换成了jakarta.*老版本的 springdoc 根本没适配直接跑不起来。!-- Spring Boot 2.x -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-ui/artifactId version1.7.0/version /dependency !-- Spring Boot 3.x -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.3.0/version /dependency版本号方面1.x 系列对应 Boot 22.x 系列对应 Boot 3这是大原则。具体小版本尽量跟你的 Spring Boot 小版本贴近避免出现兼容性抖动。4.2 Spring Boot 2.x 与 3.x 的关键差异除了依赖坐标路径匹配策略的差异也会影响放行规则。Spring Boot 3.x 默认使用PathPatternParser某些在 2.x 里能用的AntPathMatcher写法在 3.x 里行为会变。比如**在中间位置的匹配规则、尾部斜杠的处理都有细微区别。另外Spring Boot 3 里如果开启了spring.mvc.pathmatch.matching-strategypath_pattern_parser默认就是那匹配更严格。放行规则建议用最简单的/swagger-ui/**和/v3/api-docs/**不要玩花哨的通配越简单越不容易出错。还有一个容易忽略的点如果你自定义了server.servlet.context-pathspringdoc 生成的路径是会带上 context-path 的。放行规则写请求 URI 时要不要带 context-path取决于你用的是addPathPatterns不带 context-path还是request.getRequestURI()带 context-path。这俩混用是常见 bug 来源务必统一。4.3 和 Spring Security、Sa-Token 共存怎么放行如果你的项目用 Spring Security 而不是自研拦截器那么放行要写在 Security 的过滤链里而不是 HandlerInterceptor。典型写法http.authorizeRequests() .antMatchers(/swagger-ui/**, /swagger-ui.html, /v3/api-docs/**, /swagger-resources/**, /webjars/**, /doc.html).permitAll() .anyRequest().authenticated();注意 Security 的匹配和 MVC 的匹配是两套逻辑路径要分别放行。如果你既用了 Security 又用了自定义拦截器两处都要放缺一处照样白屏这是排查时最容易忽略的组合情况。如果是 Sa-Token它的放行是通过SaInterceptor加excludePathPatterns用法和方案B类似同样是路径要写全。无论哪种框架核心判断标准只有一个Swagger 那组请求最终能不能原样到达 springdoc 的控制器。能到就一定能渲染。5. 排查实录常见问题速查与踩坑经验前面讲的都是正确做法但现实排查往往是从一团乱麻里找线索。这一节我把实际遇到过的现象整理成速查表再分享几个反常识的经验希望帮你少走弯路。排查的总体思路是自底向上的先看浏览器 Network 里 api-docs 请求的响应状态和内容确定被谁拦了再去看对应的拦截器配置最后确认依赖和版本。不要一上来就换依赖、改版本那样很可能南辕北辙。5.1 问题现象与排查速查表页面现象浏览器控制台提示大概率原因处理方向空白页Unable to infer base urlswagger-config 被拦截放行 /v3/api-docs/**红色错误框Unable to render this definitionapi-docs 返回非 OpenAPI JSON检查拦截器是否包装了响应页面样式丢失静态资源 404swagger-ui 静态资源未放行放行 /swagger-ui/**一直转圈无报错但无内容请求被重定向到登录页检查 302 跳转部分接口不显示无报错分组配置或路径扫描问题检查 GroupedOpenApi 配置这张表基本覆盖了我遇到过的所有情况。用的时候按现象→提示→原因反向对照能快速缩小范围。特别提醒一直转圈这种情况它往往没有明显报错但是 F12 一看请求全部 302 到登录问题一目了然。5.2 几个反常识的实测经验第一个经验本地开、线上挂很多时候不是环境配置差异而是拦截器的白名单用了Profile只在某环境生效。查这个问题时先确认拦截器到底有没有在目标环境注册别急着怀疑 Swagger。第二个经验Unable to infer base url有时候并不是拦截器引起的而是你项目里存在一个全局的ResponseBodyAdvice把 springdoc 的 JSON 也包了一层。这种包装拦截器管不到因为它发生在响应写出阶段。排查方法是看 api-docs 的响应体是不是被data字段套了一层如果是就要在该 Advice 里跳过 springdoc 的响应或者按请求路径白名单排除。第三个经验某些浏览器扩展会干扰 Swagger 页面的静态资源加载导致页面渲染异常。这类问题表现为本地同样代码、换浏览器就好了。排查时可以先用无痕模式打开如果不复现就基本能排除是后端的问题。它和拦截器引起的报错要区分开别混为一谈。第四个经验springdoc 默认会扫描所有带RestController的类如果项目里有大量内部接口不想暴露可以在配置里做分组或路径过滤但这属于内容裁剪和报错打不开是两码事别因为想过滤接口而误改了放行路径。第五个经验也是我觉得最值得强调的放行路径一定要覆盖全部相关端点而且多个拦截器、Security 配置、响应包装这几层都要检查一遍。只改一处就以为搞定是这个问题反复出现的主要原因。我个人现在接手任何新项目的第一步就是先把这组 Swagger 白名单固化到配置常量和文档里后面谁加拦截器都引用它避免再有人重踩一遍。把这组路径维护好其实好处不止是让文档能打开它还让你的鉴权白名单更加清晰可控——哪些路径必须无鉴权、为什么无鉴权一目了然这在做安全评审或者交接时都是加分项也不至于留下一个隐藏的未授权入口。
返回列表