package com.xly.service; import com.fasterxml.jackson.databind.ObjectMapper; import com.xly.agent.AgentIdentity; import org.junit.jupiter.api.Test; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.util.ReflectionTestUtils; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** * 身份解析的**安全不变量**:身份只能来自 ERP 内省,失败路径一律 fail-closed。 * 这几条断言对应审计里的 Critical #1(空 token → 超管)与「客户端自称身份」根因。 */ class AuthzServiceTest { private static final ObjectMapper MAPPER = new ObjectMapper(); private AuthzService service(ErpClient erp) { JdbcTemplate jdbc = mock(JdbcTemplate.class); AuthzService s = new AuthzService(jdbc, erp); ReflectionTestUtils.setField(s, "devUserNo", "admin"); ReflectionTestUtils.setField(s, "devBrand", "B1"); ReflectionTestUtils.setField(s, "devSub", "S1"); ReflectionTestUtils.setField(s, "devUserType", "sysadmin"); ReflectionTestUtils.setField(s, "devUserIdOverride", "dev-user"); return s; } @Test void noTokenInProductionIsRejected() { ErpClient erp = mock(ErpClient.class); when(erp.devLoginEnabled()).thenReturn(false); assertNull(service(erp).resolveIdentity(null), "生产环境下无 token 必须拒绝,不能回落 dev 身份"); assertNull(service(erp).resolveIdentity(" "), "空白 token 同样必须拒绝"); } @Test void invalidTokenIsRejectedEvenWhenDevLoginEnabled() { ErpClient erp = mock(ErpClient.class); when(erp.devLoginEnabled()).thenReturn(true); when(erp.whoami(anyString())).thenReturn(null); // 过期/伪造 assertNull(service(erp).resolveIdentity("expired-token"), "无效 token 必须拒绝,绝不因为开了 dev-login 就降级成管理员"); } @Test void identityComesFromIntrospectionNotFromClient() throws Exception { ErpClient erp = mock(ErpClient.class); when(erp.whoami(anyString())).thenReturn(MAPPER.readTree( "{\"sId\":\"real-user\",\"sBrandsId\":\"real-brand\",\"sSubsidiaryId\":\"real-sub\",\"sType\":\"sysadmin\"}")); AgentIdentity id = service(erp).resolveIdentity("good-token"); assertNotNull(id); assertEquals("real-user", id.userId()); assertEquals("real-brand", id.brandsId(), "租户必须取自服务端内省结果"); assertEquals("good-token", id.token()); } @Test void noTokenWithDevLoginEnabledYieldsDevIdentity() { ErpClient erp = mock(ErpClient.class); when(erp.devLoginEnabled()).thenReturn(true); JdbcTemplate jdbc = mock(JdbcTemplate.class); when(jdbc.queryForList(anyString(), (Object[]) any())).thenReturn(java.util.List.of()); AuthzService s = new AuthzService(jdbc, erp); ReflectionTestUtils.setField(s, "devUserIdOverride", "dev-user"); ReflectionTestUtils.setField(s, "devUserType", "sysadmin"); ReflectionTestUtils.setField(s, "devBrand", "B1"); ReflectionTestUtils.setField(s, "devSub", "S1"); ReflectionTestUtils.setField(s, "devUserNo", "admin"); AgentIdentity id = s.resolveIdentity(null); assertNotNull(id, "本地开发允许无 token"); assertEquals("dev-user", id.userId()); } }