fix(admin): 补齐 /api/admin 方法级鉴权并加兜底过滤器
ShopKey/SkipPriceAsin/QueryAsin/ShopManage/ProductCategory 五个 controller 此前无任何 require* 鉴权,SecurityConfig 全局 permitAll 且 nginx 无 ACL, 公网 18080 上可匿名读写店铺密钥等敏感数据。统一补 requireAdminOrInternal; 其中 ShopManage/SkipPrice/QueryAsin 原先信任调用方传入 operatorId/superAdmin, 改为从已认证 principal 推导,消除传 superAdmin=true 越权扩大数据范围的问题。 新增 AdminApiGuardFilter 对 /api/admin/** 兜底要求有效身份(JWT 或内部令牌), 防止后续新增端点遗漏鉴权再裸奔;支持 admin-guard-enabled/exempt-prefixes 配置。
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
package com.nanri.aiimage.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* /api/admin 兜底鉴权过滤器单测:非 admin 路径放行、OPTIONS/豁免前缀放行、
|
||||
* 匿名拒绝返回与全局一致的 401 体、紧急开关可整体关闭。
|
||||
*/
|
||||
class AdminApiGuardFilterTest {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private AdminApiGuardFilter newFilter(AdminAuthSupport adminAuthSupport, boolean enabled, String exemptPrefixes) {
|
||||
AdminApiGuardFilter filter = new AdminApiGuardFilter(adminAuthSupport, objectMapper);
|
||||
ReflectionTestUtils.setField(filter, "enabled", enabled);
|
||||
ReflectionTestUtils.setField(filter, "exemptPrefixes", exemptPrefixes);
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Test
|
||||
void nonAdminApiPathIsNotGuarded() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, true, "");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/admin.html");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(authSupport, never()).requireUserOrInternal(any());
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void validIdentityPassesThrough() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
when(authSupport.requireUserOrInternal(any())).thenReturn(new AdminUserEntity());
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, true, "");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/admin/users");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
assertThat(response.getContentAsString()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void anonymousAdminApiRequestRejectedWith401Body() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
when(authSupport.requireUserOrInternal(any()))
|
||||
.thenThrow(new BusinessException(401, "未登录"));
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, true, "");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/admin/shop-keys");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
JsonNode body = objectMapper.readTree(response.getContentAsString());
|
||||
assertThat(body.path("success").asBoolean()).isFalse();
|
||||
assertThat(body.path("code").asInt()).isEqualTo(401);
|
||||
assertThat(chain.getRequest()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void optionsPreflightIsExempt() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, true, "");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/api/admin/shop-keys");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(authSupport, never()).requireUserOrInternal(any());
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void exemptPrefixPassesThrough() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, true, "/api/admin/shop-credential-checks");
|
||||
MockHttpServletRequest request =
|
||||
new MockHttpServletRequest("GET", "/api/admin/shop-credential-checks/poll");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(authSupport, never()).requireUserOrInternal(any());
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledGuardPassesEverything() throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
AdminApiGuardFilter filter = newFilter(authSupport, false, "");
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/api/admin/shop-keys");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
MockFilterChain chain = new MockFilterChain();
|
||||
|
||||
filter.doFilter(request, response, chain);
|
||||
|
||||
verify(authSupport, never()).requireUserOrInternal(any());
|
||||
assertThat(chain.getRequest()).isNotNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user