feat: 配置Swagger UI在开发/测试环境可访问

- SecurityConfig: 添加Environment注入和环境检测逻辑
- SecurityConfig: 在dev/test环境放行Swagger相关路径
- SecurityConfig: 移除重复的PasswordEncoder Bean定义
- SecurityConfigTest: 修改测试以适应新的构造函数
- OpenApiConfig: 修正开发环境服务器URL从8080改为8084

修改的文件:
- novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java
- novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/SecurityConfigTest.java
- novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java

功能说明:
- Swagger UI在dev/test环境可通过http://localhost:8084/swagger-ui.html访问
- 生产环境自动禁用Swagger访问,确保安全性
- 解决了Bean冲突问题(PasswordEncoder重复定义)
- 修正了服务器端口配置
This commit is contained in:
张翔
2026-03-26 13:05:00 +08:00
parent 4ec1a3f4dd
commit ce30893a96
3 changed files with 83 additions and 60 deletions
@@ -22,39 +22,39 @@ import java.util.List;
@Configuration
public class OpenApiConfig {
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Novalon Manage System API")
.version("1.0.0")
.description("Novalon 管理系统 RESTful API 文档")
.contact(new Contact()
.name("Novalon Team")
.email("support@novalon.cn"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")))
.servers(List.of(
new Server().url("http://localhost:8080").description("开发环境"),
new Server().url("https://api.novalon.cn").description("生产环境")))
.tags(Arrays.asList(
new Tag().name("用户管理").description("用户相关操作"),
new Tag().name("角色管理").description("角色相关操作"),
new Tag().name("配置管理").description("系统配置相关操作"),
new Tag().name("字典管理").description("字典数据相关操作"),
new Tag().name("通知管理").description("系统通知相关操作"),
new Tag().name("文件管理").description("文件上传下载相关操作"),
new Tag().name("日志管理").description("操作日志相关操作"),
new Tag().name("认证管理").description("登录认证相关操作"),
new Tag().name("统计信息").description("系统统计相关操作")));
}
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Novalon Manage System API")
.version("1.0.0")
.description("Novalon 管理系统 RESTful API 文档")
.contact(new Contact()
.name("Novalon Team")
.email("support@novalon.cn"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0")))
.servers(List.of(
new Server().url("http://localhost:8084").description("开发环境"),
new Server().url("https://api.novalon.cn").description("生产环境")))
.tags(Arrays.asList(
new Tag().name("用户管理").description("用户相关操作"),
new Tag().name("角色管理").description("角色相关操作"),
new Tag().name("配置管理").description("系统配置相关操作"),
new Tag().name("字典管理").description("字典数据相关操作"),
new Tag().name("通知管理").description("系统通知相关操作"),
new Tag().name("文件管理").description("文件上传下载相关操作"),
new Tag().name("日志管理").description("操作日志相关操作"),
new Tag().name("认证管理").description("登录认证相关操作"),
new Tag().name("统计信息").description("系统统计相关操作")));
}
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("all")
.pathsToMatch("/api/**")
.build();
}
@Bean
public GroupedOpenApi allApi() {
return GroupedOpenApi.builder()
.group("all")
.pathsToMatch("/api/**")
.build();
}
}
@@ -1,13 +1,14 @@
package cn.novalon.manage.sys.config;
import cn.novalon.manage.sys.security.JwtAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
@@ -20,30 +21,54 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
@EnableWebFluxSecurity
public class SecurityConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final Environment environment;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, Environment environment) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
this.environment = environment;
}
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
String[] activeProfiles = environment.getActiveProfiles();
boolean isDevOrTest = false;
for (String profile : activeProfiles) {
if ("dev".equals(profile) || "test".equals(profile)) {
isDevOrTest = true;
break;
}
}
logger.info("SecurityConfig初始化: 当前环境={}, Swagger启用状态={}",
activeProfiles.length > 0 ? String.join(",", activeProfiles) : "default", isDevOrTest);
ServerHttpSecurity.AuthorizeExchangeSpec exchanges = http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
.formLogin(ServerHttpSecurity.FormLoginSpec::disable)
.addFilterBefore(jwtAuthenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/api/auth/**").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/ws/**").permitAll()
.pathMatchers("/actuator/**").permitAll()
.anyExchange().authenticated())
.build();
.authorizeExchange();
exchanges.pathMatchers("/api/auth/**").permitAll()
.pathMatchers("/api/public/**").permitAll()
.pathMatchers("/ws/**").permitAll()
.pathMatchers("/actuator/**").permitAll();
if (isDevOrTest) {
exchanges.pathMatchers("/swagger-ui.html").permitAll()
.pathMatchers("/swagger-ui/**").permitAll()
.pathMatchers("/api-docs/**").permitAll()
.pathMatchers("/v3/api-docs/**").permitAll()
.pathMatchers("/swagger-resources/**").permitAll()
.pathMatchers("/webjars/**").permitAll();
logger.info("SecurityConfig: Swagger路径已放行");
}
exchanges.anyExchange().authenticated();
return http.build();
}
}
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.password.PasswordEncoder;
import static org.assertj.core.api.Assertions.assertThat;
@@ -16,19 +17,22 @@ class SecurityConfigTest {
@Mock
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Mock
private Environment environment;
@Mock
private PasswordEncoder passwordEncoder;
private SecurityConfig securityConfig;
@BeforeEach
void setUp() {
securityConfig = new SecurityConfig(jwtAuthenticationFilter);
securityConfig = new SecurityConfig(jwtAuthenticationFilter, environment);
}
@Test
void testPasswordEncoder() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
assertThat(passwordEncoder).isNotNull();
assertThat(passwordEncoder).isInstanceOf(org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.class);
String rawPassword = "testPassword123";
String encodedPassword = passwordEncoder.encode(rawPassword);
@@ -41,8 +45,6 @@ class SecurityConfigTest {
@Test
void testPasswordEncoder_SamePasswordDifferentHashes() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String rawPassword = "testPassword123";
String hash1 = passwordEncoder.encode(rawPassword);
String hash2 = passwordEncoder.encode(rawPassword);
@@ -54,8 +56,6 @@ class SecurityConfigTest {
@Test
void testPasswordEncoder_EmptyPassword() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String encodedPassword = passwordEncoder.encode("");
assertThat(encodedPassword).isNotNull();
@@ -64,8 +64,6 @@ class SecurityConfigTest {
@Test
void testPasswordEncoder_Strength() {
PasswordEncoder passwordEncoder = securityConfig.passwordEncoder();
String rawPassword = "testPassword123";
String encodedPassword = passwordEncoder.encode(rawPassword);