Swagger 文档在本地开发时是好东西,但项目一旦部署到测试环境或者公网服务器,直接裸奔不设防,那就等于把接口结构、字段定义、甚至生产环境的数据库表信息全给交出去了。我自己就见过不少团队把 Swagger-UI 直接暴露到线上,被爬虫扫到之后数据接口被刷爆,最后只能紧急下线版本。给 swagger-ui.html 加一个登录页面挡在门口,是成本最低、见效最快的一种保护手段。
这篇文章我会基于 SpringBoot 项目里最常见的三种做法展开:第一,用 Spring Security + 默认登录页做内存认证;第二,用原生 Filter / 拦截器 做一次轻量级鉴权;第三,结合 Vue3 前后端分离项目时,登录页面联动与 API 文档保护的思路。顺带把 SpringBoot 2.x 和 3.x 的配置差异讲清楚,因为这个问题让不少人踩过坑。
1. 项目场景拆解:为什么 Swagger 文档必须加一道登录门
1.1 标题背后反映的典型痛点
先把这个需求背后的实际场景拆开看。标题里明确写了“SpringBoot项目 访问 swagger-ui.html 添加登录页面”,这在真实项目里就是一句话:我已经把接口文档集成到项目里了,但不想让任何拿到 URL 的人直接看到接口文档内容。这个诉求一般出现在三类环境里:
- 开发环境:联调时前端、测试、后端多人共享接口信息,但不是所有人都应该看到全部接口,也方便记录谁在什么时间访问过文档。
- 测试环境:测试同学要验证接口,但是接口文档不该暴露给外部无关人员。
- 生产环境:这种场景比较紧急,如果 Swagger 已经不小心带上线了,加一道登录页面是给系统“补漏”的兜底操作。
很多初学 SpringBoot 的开发者会把注意力放在“如何把 Swagger 集成进项目”上,却很少思考“如何控制 Swagger 谁能看”。实际工作中,接口文档暴露的影响面比大部分人想象的严重得多。我记得有一次帮一个朋友排查问题,他的项目在测试服务器上被第三方拿 Swagger 文档扫描之后,直接对着接口文档里的字段名做了批量试探请求,虽然没造成数据丢失,但日志里多了几千条异常记录,排查起来非常头疼。
1.2 直接使用默认登录表单还是自建登录页
给 swagger-ui.html 加登录页,第一个要明确的方案选择是:用 Spring Security 框架自带的默认表单登录,还是自己写一个登录页面。
前者改造量很小,Spring Security 会自动渲染一个默认的登录页面,输入用户名和密码之后通过 Session 维持登录态,然后允许访问受保护的资源路径。后者需要自己写登录页 HTML、登录接口、会话管理逻辑,适合对登录页样式有要求、或者项目本身已经有一套用户体系的场景。
我给出的建议是:如果只是为了“挡住不该看的人”,用 Spring Security 默认登录页就够了;如果项目里已经存在用户表,那可以引入 Spring Security 并自定义 UserDetailsService,让 Swagger 文档的访问控制复用现有账号体系。这篇文章里会先讲基于内存用户的极简打法,因为这是最快解决“裸奔”问题的方式。
提示:这篇文章里的代码案例基于 Spring Boot 2.7.x 和 Spring Boot 3.x 两个版本分别说明,两个版本之间的 Security 配置写法差异很大,后面会单独拿一节来讲,避免你对着新版本的代码去搜老版本的教程,越看越糊涂。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 极简方案初体验:Spring Security 默认登录页保安全
2.1 引入依赖与版本选择
如果你用的是 Spring Initializr 创建的项目,引入 Spring Security 只要加一个依赖。Maven 项目在 pom.xml 里加这一段:
xml复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Gradle 项目就在 build.gradle 的 dependencies 里加:
gradle复制implementation 'org.springframework.boot:spring-boot-starter-security'
这里有一个非常关键的版本背景:Spring Boot 2.7.x 系列默认对应的 Spring Security 是 5.7.x,而 Spring Boot 3.x 对应的 Spring Security 是 6.x。两者之间的配置方式发生了巨大变化,其中最核心的变化是 WebSecurityConfigurerAdapter 这个类被彻底废弃了。早期网上流传的大部分 Spring Security 教程都在继承这个类,如果你拿 SpringBoot 3.x 项目去跑那些老代码,会发现编译直接报错。
热词里也提到了“springboot版本太高”这种说法,本质上就是版本升级之后,旧写法失效带来的学习成本。我的实际经验是:学习阶段尽量把 SpringBoot 版本固定在 2.7.x 或者 3.2.x,不要追最新。2.7.x 的社区资料最多,3.2.x 的写法更贴近未来趋势,但踩坑会多一些。
2.2 极简配置类:全局保护与路径放行
引入依赖之后,什么都不配置的情况下,Spring Security 会默认把项目里所有接口都保护起来,包括 Swagger 文档页面。启动时控制台会打印一串随机密码,配合默认用户名 user 才能登录。这其实已经达到了“访问 swagger-ui.html 需要登录”的效果,但它有一个副作用:你的业务接口也全部需要登录才能访问,这显然不是我们想要的。
所以需要自己写一个配置类,把需要放行的路径放行,把需要保护的路径保护起来。先给一个 Spring Boot 2.7.x 时代比较经典的写法:
java复制import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
UserDetails admin = User.builder()
.username("admin")
.password(passwordEncoder.encode("admin123"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 放行 swagger 相关的静态资源,这样页面本身能加载
.antMatchers(
"/swagger-ui.html",
"/swagger-ui/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/webjars/**"
).authenticated()
// 其余请求全部放行,不拦截业务接口
.anyRequest().permitAll()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.csrf(csrf -> csrf.disable())
.httpBasic(with -> {});
return http.build();
}
}
这段配置达到了几个效果:
- 访问
/swagger-ui.html以及 Swagger 页面背后加载的静态资源时,必须先登录。 - 业务接口都放行,不会影响前后端联调。
