From 7579d3163ae1cae1208ab206adfff1b92eb6bcdf Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 21 Oct 2026 11:46:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(security):=20=E6=94=BE=E8=A1=8C?= =?UTF-8?q?=E8=AF=84=E8=AE=BA=E6=95=B0=E6=8E=A5=E5=8F=A3=E5=8C=BF=E5=90=8D?= =?UTF-8?q?=E8=AE=BF=E9=97=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppCommentCountAnonymousSecurityTest.java | 106 ++++++++++++++++++ .../framework/security/SecurityConfig.java | 1 + 2 files changed, 107 insertions(+) create mode 100644 fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCommentCountAnonymousSecurityTest.java diff --git a/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCommentCountAnonymousSecurityTest.java b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCommentCountAnonymousSecurityTest.java new file mode 100644 index 0000000..c34c106 --- /dev/null +++ b/fruitshorts-app-api/src/test/java/com/fruitshorts/app/controller/AppCommentCountAnonymousSecurityTest.java @@ -0,0 +1,106 @@ +// Author: fruit.com.cn + +package com.fruitshorts.app.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fruitshorts.framework.log.AccessDeniedRecorder; +import com.fruitshorts.framework.security.JwtAuthenticationFilter; +import com.fruitshorts.framework.security.JwtTokenProvider; +import com.fruitshorts.framework.security.RestAuthEntryPoint; +import com.fruitshorts.framework.security.SecurityConfig; +import com.fruitshorts.interaction.service.CommentService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** 评论总数接口的安全链契约:匿名只读请求必须直接进入控制器。 */ +@ExtendWith(SpringExtension.class) +@WebAppConfiguration +@ContextConfiguration(classes = AppCommentCountAnonymousSecurityTest.TestConfig.class) +class AppCommentCountAnonymousSecurityTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private CommentService commentService; + + @BeforeEach + void setUp() { + given(commentService.countByDrama(7L)).willReturn(9L); + } + + @Test + void should_allow_anonymous_count_request_through_security_chain() throws Exception { + mockMvc.perform(get("/app/comments/count").param("dramaId", "7")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(9)); + } + + @Configuration + @EnableWebMvc + @EnableWebSecurity + @Import(SecurityConfig.class) + static class TestConfig { + + @Bean + CommentService commentService() { + return mock(CommentService.class); + } + + @Bean + AppCommentController appCommentController(CommentService commentService) { + return new AppCommentController(commentService); + } + + @Bean + JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter( + mock(JwtTokenProvider.class), + mock(ObjectProvider.class)); + } + + @Bean + RestAuthEntryPoint restAuthEntryPoint(ObjectMapper objectMapper) { + return new RestAuthEntryPoint( + objectMapper, + new AccessDeniedRecorder(mock(ObjectProvider.class))); + } + + @Bean + ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + MockMvc mockMvc(WebApplicationContext context) { + FilterChainProxy securityFilter = + context.getBean("springSecurityFilterChain", FilterChainProxy.class); + return MockMvcBuilders.webAppContextSetup(context) + .addFilters(securityFilter) + .build(); + } + } +} diff --git a/fruitshorts-framework/src/main/java/com/fruitshorts/framework/security/SecurityConfig.java b/fruitshorts-framework/src/main/java/com/fruitshorts/framework/security/SecurityConfig.java index 34c7f8f..0496693 100644 --- a/fruitshorts-framework/src/main/java/com/fruitshorts/framework/security/SecurityConfig.java +++ b/fruitshorts-framework/src/main/java/com/fruitshorts/framework/security/SecurityConfig.java @@ -74,6 +74,7 @@ public class SecurityConfig { .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() /* 评论列表允许匿名浏览;发布/删除仍需登录(未列入白名单即需鉴权) */ .requestMatchers(HttpMethod.GET, "/app/comments").permitAll() + .requestMatchers(HttpMethod.GET, "/app/comments/count").permitAll() .requestMatchers(PUBLIC_PATHS).permitAll() .anyRequest().authenticated()) .exceptionHandling(handling -> handling From 708b39ffcc6665aa90f351e9822d18ad41dd2e82 Mon Sep 17 00:00:00 2001 From: Zhou Weidong <502333560@qq.com> Date: Wed, 21 Oct 2026 11:46:25 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(progress):=20=E8=AE=B0=E5=BD=95=20K85?= =?UTF-8?q?=20=E8=AF=84=E8=AE=BA=E6=95=B0=E5=8C=BF=E5=90=8D=E6=94=BE?= =?UTF-8?q?=E8=A1=8C=E8=A1=A5=E4=BF=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/PROGRESS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/plans/PROGRESS.md b/docs/plans/PROGRESS.md index aae6097..ca95fdc 100644 --- a/docs/plans/PROGRESS.md +++ b/docs/plans/PROGRESS.md @@ -22958,3 +22958,13 @@ $ flutter test ``` 解读:客户端静态检查零告警;494 条测试通过,2 条需要本机联调服务的用例按既有保护跳过。K85 A/B 均已完成合并与推送,真机验收由用户执行。 + +## 2026-10-21 K85 补修:评论数接口匿名放行 + +- 验收问题:`GET /app/comments/count` 未登录调用返回业务码 401。 +- 根因:`SecurityConfig` 只放行评论列表 `GET /app/comments`,新增的 `/app/comments/count` 落入默认登录校验。 +- 修复:增加 `GET /app/comments/count` 的 `permitAll()`,保持评论列表与评论总数一致的匿名只读语义;发布、删除等写接口仍要求登录。 +- 新增 `AppCommentCountAnonymousSecurityTest`,装配真实 `SecurityConfig` 安全过滤链,未登录请求必须进入控制器并返回 `code=0`,避免仅测控制器时漏掉路由放行问题。 +- 定向安全链测试通过:`Tests run: 1, Failures: 0, Errors: 0`。 +- 功能提交:`7579d31 fix(security): 放行评论数接口匿名访问`。 +- 完整门禁通过:JDK 21 下 14 个模块 `BUILD SUCCESS`,app-api `Tests run: 69, Failures: 0, Errors: 0`;全仓仅保留既有 ffmpeg smoke 用例 1 条环境跳过。