
1. 项目概述为什么你的Swagger需要一把锁如果你用过Swagger UI肯定对那个清爽的API文档界面印象深刻。它把后端接口的结构、参数、返回值都可视化地展示出来前后端联调、测试的时候别提多方便了。但方便的另一面就是风险。默认情况下Swagger UI是没有任何访问控制的只要知道地址任何人都能打开、查看甚至直接调用你的接口。想象一下你的开发环境、测试环境甚至是不小心暴露在公网上的预发布环境里面的接口文档和调试工具就这么敞开着这无异于把自家后院的钥匙插在门上。我见过不少团队图省事直接把带Swagger的应用部署到了测试服务器结果被扫描工具扫到接口信息一览无余。轻则泄露业务逻辑重则可能被恶意调用造成数据污染甚至安全漏洞。所以给Swagger加个访问密码不是什么“高级功能”而是一个合格开发者应该具备的基本安全意识。这就像你家的Wi-Fi你不会设置一个空密码让邻居随便连吧给Swagger加锁也是同样的道理。这个“锁”的核心目标很简单在访问Swagger UI页面时弹出一个登录框要求输入正确的用户名和密码验证通过后才能看到文档内容。实现方式多种多样从最简单的Spring Security基础认证到整合公司统一的单点登录再到利用网关层做统一的访问控制都是可行的路径。今天我就以最常用、最直接的Spring Boot Spring Security方案为例带你从头到尾实现一遍并分享几个我踩过坑才总结出来的配置技巧和避坑指南。2. 核心方案选型与设计思路给Swagger加访问控制听起来简单但具体怎么做取决于你的技术栈、项目阶段和安全要求。不同的方案复杂度和适用场景完全不同。2.1 主流方案对比与选型理由在动手之前我们先理清几种常见的实现路径应用层拦截本次详解在Spring Boot应用内部通过Spring Security等安全框架对访问/swagger-ui/**、/v3/api-docs/**等路径的请求进行拦截和认证。这是最经典、最可控的方式。网关层统一管控如果项目使用了API网关如Spring Cloud Gateway, Nginx可以在网关层面配置针对Swagger路径的访问控制比如Basic Auth、IP白名单、或与统一认证中心对接。这种方式将安全与业务解耦适合微服务架构。容器/服务器层面控制在Tomcat、Nginx等Web服务器或Docker容器中配置访问限制。这种方式更底层不依赖应用代码但灵活性稍差。Swagger原生配置有限Swagger UI本身提供了一些简单的安全配置选项但通常功能较弱难以满足复杂的认证需求。为什么我首选Spring Security方案对于大多数处于开发或测试阶段的单体或小型微服务Spring Boot项目来说在应用内集成Spring Security是最快、最直接、学习成本最低的选择。它不需要引入额外的中间件配置集中调试方便并且能与Spring Boot生态无缝集成。我们今天的目标是快速解决问题所以这个方案最合适。2.2 技术栈与依赖确认我们的演示环境基于以下技术栈这也是目前Java领域最主流的组合Spring Boot: 2.7.x 或 3.x.x (两者配置有细微差异下文会指出)Spring Security: 5.x 或 6.x (与Spring Boot版本对应)SpringDoc OpenAPI: 1.7.x (用于替代老旧的Springfox Swagger)Java 8这里特别强调一下SpringDoc OpenAPI。如果你还在用springfox-swagger2我强烈建议你迁移到SpringDoc。它不仅支持更新的OpenAPI 3.0规范而且与Spring Boot 3兼容性更好社区活跃。我们接下来的配置也基于SpringDoc。Maven核心依赖如下!-- SpringDoc OpenAPI (Swagger UI) -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-ui/artifactId version1.7.0/version !-- 请检查最新版本 -- /dependency !-- Spring Security -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency只要加入这两个依赖你的项目就具备了提供Swagger UI和基础安全认证的能力。2.3 安全设计要点在设计时我们需要明确几个关键点保护哪些路径至少要保护Swagger UI的HTML页面路径通常是/swagger-ui.html或/swagger-ui/index.html和API文档JSON的提供路径/v3/api-docs及其子路径。认证方式我们采用最简单的HTTP Basic认证。用户在浏览器访问Swagger页面时会弹出一个原生登录框。用户从哪里来为了演示我们在内存中配置一个固定的用户名和密码。在实际生产或测试环境中你应该从数据库或配置中心读取用户信息。其他接口是否需要保护这是一个重要的决策点。通常我们只希望给Swagger加密码而业务API接口如/api/**在开发测试环境可能不需要认证或者使用另一套Token机制。我们需要在Spring Security的配置中精确区分这两类路径。3. 一步步实现Swagger密码访问控制理论清晰了我们开始动手。我会按照从配置到验证的顺序详细说明每一步。3.1 基础安全配置类首先创建一个Spring Security的配置类。这是整个功能的核心。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; import static org.springframework.security.config.Customizer.withDefaults; Configuration EnableWebSecurity public class SwaggerSecurityConfig { /** * 配置安全过滤链定义哪些路径需要保护哪些可以放行。 * 这是Spring Security 5.7 / Spring Boot 2.7 推荐的Lambda DSL配置风格更简洁。 */ Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz // 1. 精确匹配Swagger UI相关的资源路径要求认证 .requestMatchers(/swagger-ui.html).authenticated() .requestMatchers(/swagger-ui/**).authenticated() .requestMatchers(/v3/api-docs).authenticated() .requestMatchers(/v3/api-docs/**).authenticated() // 2. 放行Swagger UI所需的静态资源CSS, JS等 .requestMatchers(/webjars/**, /swagger-resources/**).permitAll() // 3. 放行应用自身的健康检查、错误页面等公共端点按需配置 .requestMatchers(/actuator/health, /error).permitAll() // 4. 你的业务API路径这里示例为全部放行。实际请根据需求调整。 .requestMatchers(/api/**).permitAll() // 5. 其他所有请求默认要求认证更安全。如果只想保护Swagger可以改为.permitAll() .anyRequest().authenticated() ) // 启用HTTP Basic认证。访问受保护路径时浏览器会弹出登录框。 .httpBasic(withDefaults()) // 暂时禁用CSRF因为Swagger UI的一些操作如Try it out会触发POST请求CSRF保护会拦截它们。 // 注意在生产环境中需要更完善的CSRF处理策略。 .csrf(csrf - csrf.disable()); return http.build(); } /** * 配置一个内存用户详情服务用于演示。 * 实际项目中应替换为从数据库查询的UserDetailsService。 */ Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user User.builder() .username(admin) // 自定义用户名 .password(passwordEncoder.encode(swagger123)) // 自定义密码必须加密 .roles(SWAGGER_ADMIN) // 角色可用于更细粒度的控制 .build(); return new InMemoryUserDetailsManager(user); } /** * 密码编码器。必须配置用于对内存中的密码进行加密。 * 这里使用BCrypt这是目前推荐的安全哈希算法。 */ Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }关键点解析requestMatchers用于匹配请求路径。顺序很重要更具体的规则应该放在前面。.authenticated()表示匹配的路径需要认证登录后才能访问。.permitAll()表示匹配的路径允许所有人直接访问无需认证。/webjars/**和/swagger-resources/**这是Swagger UI前端页面加载CSS、JavaScript等静态资源的路径。必须放行否则即使登录成功Swagger页面也无法正常加载样式和功能。CSRF禁用这是一个权衡。Swagger UI的“Execute”按钮会发送POST/PUT/DELETE请求如果开启CSRF需要额外处理Token会使演示变复杂。在纯内部开发/测试环境可以暂时禁用。若用于稍公开的环境建议研究如何集成CSRF Token。3.2 集成SpringDoc OpenAPI配置接下来我们配置SpringDoc确保它能与Spring Security共存并且能正确找到受保护的API文档端点。创建一个配置类来定制SpringDocimport io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class OpenApiConfig { Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(你的项目API文档) .version(1.0) .description(这是一个受保护的Swagger文档需要登录访问。)); } }这个配置不是安全必需的但它能让你的Swagger文档页面标题和描述更友好。一个至关重要的补充配置解决常见空白页问题Spring Security默认会为所有请求添加一些安全头部有时会干扰Swagger UI的运行。如果你发现登录后Swagger页面是空白的或者控制台有CORS/Content Security Policy错误可以在SecurityFilterChain配置中添加以下内容// 在 http.httpBasic(withDefaults()) 之后添加 .headers(headers - headers .contentSecurityPolicy(csp - csp .policyDirectives(script-src self unsafe-inline unsafe-eval; object-src self;) ) .frameOptions(frame - frame.sameOrigin()) // 允许同源iframe嵌入 )这段配置放宽了内容安全策略允许Swagger UI所需的行内脚本执行。3.3 验证与访问完成以上配置后启动你的Spring Boot应用。访问Swagger UI打开浏览器输入http://localhost:8080/swagger-ui.html(或你的应用上下文路径)。弹出登录框此时浏览器会弹出一个标准的HTTP Basic认证对话框要求输入用户名和密码。输入凭证输入我们在UserDetailsService中配置的用户名admin和密码swagger123。成功访问验证通过后你将正常看到Swagger UI界面所有API文档一览无余。注意如果你在登录后看到Swagger页面但API列表处显示“Failed to load API definition”或“Fetch error”并指向/v3/api-docs这通常意味着/v3/api-docs这个路径没有被正确纳入保护或放行规则。请回头仔细检查SecurityFilterChain中requestMatchers对/v3/api-docs和/v3/api-docs/**的配置确保它们被.authenticated()了。因为Swagger UI页面本身和获取JSON数据的请求是分开的两者都需要认证。4. 高级配置与生产级考量上面的配置能跑起来但离“好用”和“安全”还差几步。下面分享几个进阶配置点。4.1 从配置文件读取凭证把用户名密码硬编码在Java代码里是极不推荐的。我们应该放到application.yml或application.properties中。application.yml配置swagger: auth: username: admin password: swagger123# # 包含特殊字符时用单引号包裹修改UserDetailsServiceimport org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class SecurityConfig { Value(${swagger.auth.username}) private String swaggerUsername; Value(${swagger.auth.password}) private String swaggerPassword; Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails user User.builder() .username(swaggerUsername) .password(passwordEncoder.encode(swaggerPassword)) // 密码仍需加密 .roles(SWAGGER_USER) .build(); return new InMemoryUserDetailsManager(user); } // ... 其他Bean定义 }4.2 区分环境仅在某些环境启用密码我们可能只想在测试、预发布环境加密码本地开发环境则希望直接访问。可以通过Profile和条件化配置来实现。方案一使用Profile创建两个不同的安全配置类用Profile注解标记。Configuration Profile(!dev) // 非dev环境生效 EnableWebSecurity public class ProdSwaggerSecurityConfig { // 包含完整密码保护的配置 } Configuration Profile(dev) // 仅dev环境生效 EnableWebSecurity public class DevSecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(authz - authz .anyRequest().permitAll() // 开发环境全部放行 ) .csrf(csrf - csrf.disable()); return http.build(); } }启动应用时通过--spring.profiles.activedev来激活dev配置。方案二通过配置属性控制在配置文件中增加一个开关。swagger: auth: enabled: true username: admin password: secret然后在Java配置中根据这个开关动态决定是否配置认证Value(${swagger.auth.enabled:false}) private boolean swaggerAuthEnabled; Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception throws Exception { if (swaggerAuthEnabled) { // 应用带认证的配置 http.authorizeHttpRequests(authz - authz .requestMatchers(/swagger-ui/**, /v3/api-docs/**).authenticated() // ... 其他规则 ).httpBasic(withDefaults()); } else { // 放行Swagger相关路径 http.authorizeHttpRequests(authz - authz .requestMatchers(/swagger-ui/**, /v3/api-docs/**).permitAll() // ... 其他规则 ); } http.csrf(csrf - csrf.disable()); return http.build(); }4.3 整合数据库或LDAP认证内存用户只适合演示。真实项目用户信息通常存在数据库或LDAP中。你需要实现一个从数据库查询的UserDetailsService。Service public class DatabaseUserDetailsService implements UserDetailsService { Autowired private UserRepository userRepository; // 假设你的用户仓库 Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1. 从数据库根据username查询用户实体 UserEntity userEntity userRepository.findByUsername(username) .orElseThrow(() - new UsernameNotFoundException(用户不存在: username)); // 2. 将数据库中的角色字符串转换为Spring Security的GrantedAuthority ListGrantedAuthority authorities userEntity.getRoles().stream() .map(role - new SimpleGrantedAuthority(ROLE_ role)) .collect(Collectors.toList()); // 3. 构建并返回Spring Security的UserDetails对象 return new org.springframework.security.core.userdetails.User( userEntity.getUsername(), userEntity.getPassword(), // 数据库中的密码应该是加密存储的 authorities ); } }然后在安全配置类中注入这个自定义的UserDetailsServiceBean即可。5. 常见问题排查与实战技巧即使按照步骤操作你也可能会遇到一些坑。这里我整理了最常见的问题和解决方法。5.1 问题速查表问题现象可能原因解决方案访问/swagger-ui.html直接返回4041. 依赖未正确引入。2. Spring Boot 3 路径变化。3. 应用有自定义的servlet.context-path。1. 检查pom.xml中springdoc-openapi-ui依赖。2. Spring Boot 3 中默认路径是/swagger-ui/index.html。3. 访问路径应为http://host:port/context-path/swagger-ui.html。登录后Swagger页面空白控制台报JS/CSS加载失败(403)Spring Security拦截了静态资源请求。在安全配置中确保放行了/webjars/**和/swagger-resources/**路径。登录后页面显示“Failed to load API definition”/v3/api-docs路径未被认证或访问被拒。1. 检查安全配置确保/v3/api-docs和/v3/api-docs/**被authenticated()。2. 检查浏览器网络面板看对该路径的请求是否返回401。输入正确密码仍提示认证失败1. 密码编码器不匹配。2. 内存中配置的密码未加密。3. 角色名称前缀问题。1. 确保UserDetailsService中存储的密码是使用passwordEncoder().encode()加密的。2. 登录时Spring Security会用相同的PasswordEncoder比对。3. 检查角色字符串默认需要ROLE_前缀。Swagger的“Try it out”功能报403错误CSRF保护拦截了POST/PUT/DELETE请求。在开发测试环境可在安全配置中暂时.csrf().disable()。生产环境需配置CSRF Token。想禁用Swagger希望在生产环境彻底关闭。1. 使用Profile(!prod)注解在Swagger配置类上。2. 通过配置属性springdoc.api-docs.enabledfalse和springdoc.swagger-ui.enabledfalse。5.2 实操心得与避坑指南路径匹配的优先级与精确性Spring Security的匹配规则是从上到下执行第一个匹配的规则生效。一定要把最具体、最特殊的路径如/swagger-ui.html放在前面把最通用的路径如/api/**放在中间把anyRequest()放在最后。顺序错了可能会导致规则覆盖出现意想不到的放行或拦截。静态资源放行是必须的这一点我反复强调因为它太容易出错。Swagger UI不是一个简单的HTML它依赖大量前端资源。只保护/swagger-ui.html而没放行/webjars/**结果就是看到一个没有样式、没有功能的“裸”页面或者根本加载不出来。务必在配置中检查这两条放行规则。密码加密是强制要求从Spring Security 5开始{noop}前缀表示无加密虽然还能用但控制台会出警告。使用BCryptPasswordEncoder是标准做法。在内存中配置用户时密码必须通过passwordEncoder.encode(“明文密码”)处理后再存储。否则认证时会因为编码不匹配而失败。善用浏览器开发者工具遇到问题时第一时间打开浏览器的“网络”(Network)面板。查看访问swagger-ui.html、v3/api-docs以及各类.js、.css文件时的HTTP状态码。401代表未认证403代表无权限404代表路径错误。根据状态码能快速定位问题方向。环境隔离是最佳实践永远不要用一套安全配置走天下。通过Spring Profiles或条件化Bean为本地开发、测试环境、生产环境设置不同的安全策略。本地可以完全开放测试环境加简单密码生产环境则可能结合OAuth2、JWT等更复杂的方案或者直接禁用Swagger。关于CSRF的取舍在前后端分离且使用Token如JWT认证的架构中CSRF的风险相对较低因为标准做法不会将Token存在Cookie中。如果你的Swagger仅用于内部调试且业务API也是Token认证禁用CSRF以简化Swagger操作是常见的做法。但这需要你充分理解CSRF的风险和你的应用架构。