☰
Spring Boot整合Spring Security与Swagger 2.9.2:放行配置与生产环境安全实践
2026/10/14 1:15:13 网站建设 项目流程

1. 为什么这套组合至今仍值得聊

但凡做过Java后端的人,对Spring Security大概都有一种又爱又恨的复杂情绪。爱的是它确实能把认证授权这套东西给你兜住,恨的是它的配置方式在不同版本之间反复横跳,网上搜到的教程十篇里有八篇跑不起来。而Swagger这个接口文档工具,本身跟Spring Security是两条平行线,可一旦你把它们塞进同一个Spring Boot工程里,问题就来了——Swagger的那一堆静态资源和接口路径全被Security拦截了,打开文档页面直接401,或者页面能打开但接口列表加载不出来。

这个组合的坑,我在实际项目里踩过不止一次。标题里提到的Swagger 2.9.2这个版本号很关键,因为2.9.x是Swagger 2系列的最后一个大版本,之后Springfox就基本停更了,很多人转向了springdoc-openapi。但现实情况是,大量存量项目、公司内部框架、老系统升级,仍然跑在Swagger 2.9.2上。所以把这套组合的整合逻辑彻底搞清楚,不是怀旧,是实打实的生产需要。

这篇文章适合谁看?如果你正在维护一个Spring Boot 2.x的项目,用着Spring Security做权限控制,同时想接入Swagger做接口文档,结果被各种拦截规则搞得头大,那这篇就是写给你的。我会把整个整合思路、配置细节、踩坑记录全部摊开讲,代码可以直接抄。如果你用的是Spring Boot 3.x或者已经迁移到springdoc,部分配置方式会不一样,但底层的Security过滤链思路是相通的,同样有参考价值。

先说清楚一个前提:Spring Security的版本和Spring Boot的版本是绑定的。Spring Boot 2.1.x到2.3.x这个区间,对应的Spring Security是5.1到5.3左右,这个区间的配置方式以WebSecurityConfigurerAdapter为主。标题里标注的时间是2020年6月,那会儿主流就是Spring Boot 2.2.x或2.3.x配Spring Security 5.x,Swagger用2.9.2。我下面所有的配置和代码,都基于这个版本组合来展开,不会混入Spring Boot 3的写法,避免读者混淆。

2. 整合前必须想清楚的几个设计问题

2.1 为什么Swagger会被Security拦截

很多人第一次遇到Swagger页面打不开,第一反应是Swagger配置错了。其实不是。Spring Security默认会拦截所有进入应用的HTTP请求,它不管你这些请求是给业务接口的还是给文档页面的。Swagger在运行时会在你的应用里注册一批资源和端点,主要包括这几类:

  • swagger-ui.html这个页面本身
  • /swagger-resources/**下的配置资源
  • /v2/api-docs这个核心的文档数据接口
  • /webjars/**下的前端静态资源

这些路径如果不显式放行,Security就会按照默认规则要求认证,结果就是浏览器请求这些资源时被重定向到登录页或者直接返回401。所以整合的核心动作,就是在Security的过滤链里给这些路径开白名单。

2.2 放行策略的两种思路对比

放行Swagger相关路径,常见有两种做法,我把它俩摆在一起对比一下,你就知道该选哪个了。

策略做法优点缺点适用场景
全路径放行把Swagger所有相关路径加入permitAll配置简单,一次到位生产环境文档也暴露开发测试环境
按环境开关通过Profile控制Swagger是否启用,Security配置联动生产安全配置稍复杂正式项目

我的建议是:开发阶段用第一种快速跑通,项目上线前一定要切到第二种。因为Swagger文档暴露的接口信息,对攻击者来说就是一张地图,生产环境绝对不应该对外开放。后面我会给出按环境开关的完整实现。

2.3 版本兼容性这个隐形炸弹

Springfox Swagger 2.9.2对Spring Boot的版本是有要求的。如果你用的是Spring Boot 2.6及以上,会发现启动直接报错,原因是Spring Boot 2.6把路径匹配策略默认从AntPathMatcher改成了PathPatternParser,而Springfox 2.9.2不兼容这个变化。解决办法是在配置文件里把匹配策略改回去:

spring: mvc: pathmatch: matching-strategy: ant_path_matcher

这个坑我在一个升级项目里遇到过,当时排查了大半天,最后发现就是这一行配置的事。所以你在动手之前,先确认自己的Spring Boot版本,如果是2.6+,记得加上这行。

3. 依赖引入与基础配置落地

3.1 pom依赖的精确写法

依赖这块没什么花活,但版本号要对齐。Spring Boot的parent统一管理版本,Swagger的两个依赖版本保持一致。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.12.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> </dependencies>

这里有个细节值得说:springfox-swagger2是核心的文档生成库,springfox-swagger-ui是那个可视化页面的前端包。两个都要引,少一个页面就打不开。另外,2.9.2这个版本依赖了比较老的guava和spring-plugin-core,在某些项目里可能跟其他依赖冲突,如果启动时报NoSuchMethodError之类的错误,八成是依赖冲突,用mvn dependency:tree排查一下。

3.2 Swagger配置类的写法

Swagger的配置类本身不复杂,但有几个点容易写错。我直接给一份能跑的:

@Configuration @EnableSwagger2 @EnableConfigurationProperties(SwaggerProperties.class) public class SwaggerConfig { @Bean public Docket apiDocket(SwaggerProperties properties) { return new Docket(DocumentationType.SWAGGER_2) .enable(properties.isEnabled()) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.example.demo.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("业务接口文档") .description("接口说明与调试入口") .version("1.0.0") .build(); } }

@EnableSwagger2这个注解必须加,它是开启Swagger功能的开关。basePackage要指向你实际的Controller所在包,写错了文档里就是空的。.enable()这个方法接收一个布尔值,用来控制Swagger是否生效,这个就是后面做环境开关的关键。

3.3 用配置属性控制Swagger开关

为了让Swagger能在不同环境灵活开关,我习惯把它抽成一个配置属性类:

@ConfigurationProperties(prefix = "swagger") public class SwaggerProperties { private boolean enabled = true; private String title = "接口文档"; private String version = "1.0.0"; // getter 和 setter 省略 }

然后在application-dev.yml里写swagger.enabled: true,在application-prod.yml里写swagger.enabled: false。这样打包上线的时候,生产环境的Swagger自动关闭,连文档接口都不会注册,Security那边也就不需要为它操心了。这个设计比在Security里硬编码放行路径要优雅得多,因为它从源头上就把Swagger关掉了。

4. Security配置的核心细节拆解

4.1 放行路径的完整清单

这是整个整合里最关键的一步。Swagger 2.9.2需要放行的路径,我整理成一张表,你照着配就行:

路径作用是否必须放行
/swagger-ui.html文档主页面是
/swagger-ui/**新版UI资源路径是
/swagger-resources/**分组配置资源是
/v2/api-docs文档数据接口是
/webjars/**前端静态资源是
/doc.html部分UI入口视情况

这里要特别提醒:/v2/api-docs这个路径,如果你在Swagger配置里自定义了groupName或者用了多Docket分组,实际路径会变成/v2/api-docs?group=xxx这种形式,但放行的时候只需要放行/v2/api-docs这个前缀即可,查询参数不影响路径匹配。

4.2 WebSecurityConfigurerAdapter的配置写法

基于Spring Security 5.x的经典写法,我给出完整配置:

@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests() .antMatchers( "/swagger-ui.html", "/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs", "/webjars/**" ).permitAll() .antMatchers("/api/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin().permitAll() .and() .httpBasic(); } }

逐段解释一下。csrf().disable()这行在前后端分离或者纯接口项目里基本是标配,因为CSRF防护主要针对基于Session的表单场景,接口项目开着反而会导致POST请求被拦。authorizeRequests()开启授权配置,后面的antMatchers按顺序匹配,越具体的路径越要写在前面。anyRequest().authenticated()是兜底规则,除了上面放行的,其他一律要认证。

4.3 为什么放行了还是打不开

这是最高频的问题。放行路径配了,但页面还是打不开,通常有这几个原因:

第一个,路径写错了。比如把/swagger-resources/**写成了/swagger-resource/**,少个s,匹配不上。这种拼写错误肉眼很难发现,建议直接从浏览器Network面板里看实际请求的路径,复制过来。

第二个,放行规则的位置不对。antMatchers是按声明顺序匹配的,如果你把anyRequest().authenticated()写在了放行规则前面,那后面的放行就永远不生效。这个顺序问题坑过很多人。

第三个,Spring Boot 2.6+的路径匹配策略问题,前面提过了,加那行ant_path_matcher配置。

第四个,如果你用了自定义的Filter或者拦截器,可能在Security之前就把请求拦了。排查的时候可以在Filter里打日志,看请求到底走到哪一步被拦下的。

5. 完整实操流程与验证

5.1 从零搭建的步骤清单

我把整个搭建过程拆成可执行的步骤,你按顺序来:

  1. 创建Spring Boot项目,引入web、security、swagger三个依赖
  2. 写SwaggerConfig配置类,加上@EnableSwagger2
  3. 写SwaggerProperties配置属性类,支持环境开关
  4. 写SecurityConfig,放行Swagger相关路径
  5. 写一个测试Controller,加几个接口
  6. 启动项目,访问http://localhost:8080/swagger-ui.html
  7. 验证页面能打开、接口列表能加载、接口能调试

5.2 测试Controller的写法

为了验证整合效果,写个简单的Controller:

@RestController @RequestMapping("/api/user") @Api(tags = "用户管理") public class UserController { @ApiOperation("查询用户列表") @GetMapping("/list") public List<String> list() { return Arrays.asList("user1", "user2"); } @ApiOperation("根据ID查询用户") @GetMapping("/{id}") public String getById(@PathVariable Long id) { return "user-" + id; } }

@Api和@ApiOperation这两个注解是Swagger用来生成文档描述的,加上之后页面里就能看到中文说明。注意/api/user这个路径,如果它不在放行清单里,那访问接口本身是需要认证的,但文档页面能正常展示接口定义,这是正常的——文档展示和接口调用是两回事。

5.3 验证过程中的关键观察点

启动之后,打开浏览器访问文档页面,重点看这几个地方:

  • 页面标题是否显示你配置的title
  • 接口分组是否按Controller正确归类
  • 每个接口的说明文字是否显示
  • 点击"Try it out"能否正常发起请求

如果页面能打开但接口列表是空的,八成是basePackage配错了,或者Controller没加@RestController。如果接口列表有但点击调试报401,说明接口路径本身需要认证,这是Security在正常工作,不是bug。

6. 常见问题排查速查表

我把实际项目中遇到的高频问题整理成表,方便你对照排查:

现象可能原因解决方向
页面404依赖没引全或路径写错检查swagger-ui依赖和访问路径
页面401放行路径没配或顺序错检查antMatchers配置
页面能开但列表空basePackage配错核对Controller包路径
启动报错NoSuchMethod依赖冲突用dependency:tree排查
2.6+启动失败路径匹配策略变了加ant_path_matcher配置
接口调试跨域失败没配CORS在Security里开启cors

关于跨域这个补充一句,Swagger页面调试接口时,如果前后端不同源,需要在Security配置里加上.cors(),并且提供一个CorsConfigurationSource的Bean。这个在前后端分离项目里几乎必配。

7. 几个我踩过的坑和实操心得

第一个坑,关于@EnableWebSecurity和@EnableGlobalMethodSecurity的区别。前者是开启Web安全,后者是开启方法级安全注解(比如@PreAuthorize)。如果你只做URL级别的权限控制,加@EnableWebSecurity就够了。我见过有人在方法级注解不生效时反复检查注解,最后发现是忘了加@EnableGlobalMethodSecurity(prePostEnabled = true)。

第二个坑,Swagger的@ApiModelProperty注解在实体类上写的时候,example属性给的值如果类型不对,页面调试时会报参数转换错误。比如字段是Long类型,你example写了个字符串,点调试就炸。这个细节很小但很烦人,建议example的值跟字段类型严格对应。

第三个坑,关于Security的密码编码器。Spring Security 5之后,如果你配置了内存用户或者数据库用户,密码必须用BCryptPasswordEncoder加密存储,明文密码会报"Encoded password does not look like BCrypt"的警告,虽然不一定报错,但认证会失败。这个在整合Swagger时容易被忽略,因为你的注意力都在放行路径上。

第四个心得,关于文档分组。项目大了之后,一个文档页面塞几百个接口,找起来很痛苦。用多个Docket做分组,按业务模块拆开,体验会好很多。分组的写法是在Docket上设置groupName,然后每个分组指定不同的basePackage或路径匹配规则。

第五个心得,Swagger的注解虽然方便,但不要为了文档而文档。我见过有人在每个字段上都堆一堆@ApiModelProperty,写得比业务代码还长。我的原则是:接口级别的说明必须写清楚,字段级别的说明按需写,核心字段和容易误解的字段写,其他可以省略。文档是给人看的,不是给注解数量凑数的。

8. 生产环境的安全收尾

开发阶段跑通只是第一步,上线前必须做几件事。第一,把swagger.enabled在生产配置里设为false,从源头关闭。第二,如果因为某些原因生产也要保留文档(比如内部系统),那一定要给文档路径加上独立的认证,不能简单permitAll。第三,检查Security的兜底规则,确保没有意外的路径被放行。第四,把Swagger相关的依赖在打包时排除掉,减小包体积,也避免潜在的安全扫描告警。

我个人的习惯是,在application-prod.yml里明确写swagger.enabled: false,同时在Security配置里用@Profile("!prod")把放行规则也隔离掉,双保险。这样即使有人误改了配置属性,Security层面也不会放行文档路径。

这套组合的整合,说到底就是两件事:让Swagger的资源路径通过Security的检查,以及让Swagger在正确的环境里开关。把这两件事想明白,剩下的都是配置细节。我上面给的代码和配置,在Spring Boot 2.3.x + Spring Security 5.3 + Swagger 2.9.2这个组合下是验证过的,你直接拿去改改包名就能用。如果你的版本组合不一样,重点检查路径匹配策略和Security的配置方式,这两个是版本差异最大的地方。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询