From ce30893a96d67381ffcff75c370cfc910b2e875a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E7=BF=94?= Date: Thu, 26 Mar 2026 13:05:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E7=BD=AESwagger=20UI=E5=9C=A8?= =?UTF-8?q?=E5=BC=80=E5=8F=91/=E6=B5=8B=E8=AF=95=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=AF=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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重复定义) - 修正了服务器端口配置 --- .../manage/app/config/OpenApiConfig.java | 68 +++++++++---------- .../manage/sys/config/SecurityConfig.java | 57 +++++++++++----- .../manage/sys/config/SecurityConfigTest.java | 18 +++-- 3 files changed, 83 insertions(+), 60 deletions(-) diff --git a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java index 3d3e062..050a483 100644 --- a/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java +++ b/novalon-manage-api/manage-app/src/main/java/cn/novalon/manage/app/config/OpenApiConfig.java @@ -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(); + } } diff --git a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java index a6a4425..6fb835c 100644 --- a/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java +++ b/novalon-manage-api/manage-sys/src/main/java/cn/novalon/manage/sys/config/SecurityConfig.java @@ -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(); } } diff --git a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/SecurityConfigTest.java b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/SecurityConfigTest.java index 8cb5f17..ccb6ce1 100644 --- a/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/SecurityConfigTest.java +++ b/novalon-manage-api/manage-sys/src/test/java/cn/novalon/manage/sys/config/SecurityConfigTest.java @@ -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);