修复安全:默认关闭Mock开关并强化报到机与打卡接口服务端鉴权与测试环境约束
This commit is contained in:
1 parent
cecf11790e
commit
61c2b4b36c
3 files changed
+150
-5
No files matched your search
@@ -146,6 +146,6 @@ xss:
|
||||
train:
|
||||
mock:
|
||||
device:
|
||||
# 模拟设备打卡接口开关 (仅在测试/沙箱环境开启)
|
||||
enabled: true
|
||||
# 模拟设备打卡接口开关 (生产环境与公共配置必须默认关闭,仅允许在测试环境显式开启)
|
||||
enabled: false
|
||||
|
||||
+23
-3
@@ -91,8 +91,18 @@ public class ReportTerminalServiceImpl implements ReportTerminalService
|
||||
@Autowired(required = false)
|
||||
private TrainElectronicBoardMapper trainElectronicBoardMapper;
|
||||
|
||||
@Value("${train.mock.device.enabled:true}")
|
||||
private boolean mockDeviceEnabled;
|
||||
@Value("${train.mock.device.enabled:false}")
|
||||
private boolean mockDeviceEnabled = false;
|
||||
|
||||
public void setMockDeviceEnabled(boolean mockDeviceEnabled)
|
||||
{
|
||||
this.mockDeviceEnabled = mockDeviceEnabled;
|
||||
}
|
||||
|
||||
public boolean isMockDeviceEnabled()
|
||||
{
|
||||
return this.mockDeviceEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReportTerminalPreviewVO lookupTerminalReport(String studentId, String planId)
|
||||
@@ -238,6 +248,10 @@ public class ReportTerminalServiceImpl implements ReportTerminalService
|
||||
{
|
||||
throw new ServiceException("确认报到请求参数不能为空");
|
||||
}
|
||||
if (operator == null || operator.trim().isEmpty() || "anonymousUser".equalsIgnoreCase(operator))
|
||||
{
|
||||
throw new ServiceException("操作员未通过身份认证,拒绝执行报到确认");
|
||||
}
|
||||
|
||||
// 1. 定位 enrollmentId
|
||||
Long enrollmentId = dto.getEnrollmentId();
|
||||
@@ -294,12 +308,18 @@ public class ReportTerminalServiceImpl implements ReportTerminalService
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MockAttendanceResultVO mockDeviceAttendance(MockAttendanceDTO dto, String operator)
|
||||
{
|
||||
// 1. 模拟环境开关保护
|
||||
// 1. 模拟环境开关保护 (公共配置默认关闭,仅允许在独立测试环境开启)
|
||||
if (!mockDeviceEnabled)
|
||||
{
|
||||
throw new ServiceException("模拟设备打卡接口已禁用(仅允许在测试环境开启)");
|
||||
}
|
||||
|
||||
// 2. 服务端鉴权:操作人不能未认证或匿名
|
||||
if (operator == null || operator.trim().isEmpty() || "anonymousUser".equalsIgnoreCase(operator))
|
||||
{
|
||||
throw new ServiceException("操作员未通过身份认证,拒绝执行模拟打卡");
|
||||
}
|
||||
|
||||
if (dto == null || dto.getStudentId() == null || dto.getStudentId().trim().isEmpty() || dto.getLessonId() == null)
|
||||
{
|
||||
throw new ServiceException("模拟打卡参数不完整:studentId 和 lessonId 必填");
|
||||
|
||||
+125
@@ -176,6 +176,9 @@ public class ReportTerminalServiceTest
|
||||
|
||||
// Wire Mappers
|
||||
setupMappers();
|
||||
|
||||
// 隔离测试环境显式开启模拟打卡开关
|
||||
terminalService.setMockDeviceEnabled(true);
|
||||
}
|
||||
|
||||
private void setupMappers() throws Exception
|
||||
@@ -617,6 +620,128 @@ public class ReportTerminalServiceTest
|
||||
assertEquals("报到记录依然仅为最初的 1 条", 1, reportsByEnrollment.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全测试 1:当 Mock 开关关闭时(默认关闭状态),模拟打卡接口必须被明确拒绝。
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityMockDisabledByDefaultThrowsException()
|
||||
{
|
||||
// 显式关闭 Mock 开关(模拟公共配置/默认生产环境状态)
|
||||
terminalService.setMockDeviceEnabled(false);
|
||||
assertFalse("Mock开关应处于关闭状态", terminalService.isMockDeviceEnabled());
|
||||
|
||||
MockAttendanceDTO dto = new MockAttendanceDTO();
|
||||
dto.setStudentId("S101");
|
||||
dto.setLessonId(601L);
|
||||
dto.setActionType("SIGN_IN");
|
||||
|
||||
try
|
||||
{
|
||||
terminalService.mockDeviceAttendance(dto, "admin");
|
||||
fail("Mock 开关关闭时,调用模拟设备打卡接口必须抛出异常");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue("应包含接口已禁用提示", se.getMessage().contains("模拟设备打卡接口已禁用"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全测试 2:模拟打卡接口要求服务端操作员认证,匿名或未认证调用必须被拒绝。
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityUnauthenticatedOperatorRejectedOnMockAttendance()
|
||||
{
|
||||
terminalService.setMockDeviceEnabled(true);
|
||||
MockAttendanceDTO dto = new MockAttendanceDTO();
|
||||
dto.setStudentId("S101");
|
||||
dto.setLessonId(601L);
|
||||
dto.setActionType("SIGN_IN");
|
||||
|
||||
// 匿名操作员测试
|
||||
try
|
||||
{
|
||||
terminalService.mockDeviceAttendance(dto, "anonymousUser");
|
||||
fail("匿名操作员执行打卡必须被拒绝");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue(se.getMessage().contains("未通过身份认证"));
|
||||
}
|
||||
|
||||
// 空操作员测试
|
||||
try
|
||||
{
|
||||
terminalService.mockDeviceAttendance(dto, null);
|
||||
fail("未携带操作员凭证必须被拒绝");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue(se.getMessage().contains("未通过身份认证"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全测试 3:报到机确认接口要求服务端操作员认证,匿名或空操作员必须被拒绝。
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityUnauthenticatedOperatorRejectedOnConfirmReport()
|
||||
{
|
||||
ReportTerminalConfirmDTO dto = new ReportTerminalConfirmDTO();
|
||||
dto.setEnrollmentId(1001L);
|
||||
dto.setStudentId("S101");
|
||||
dto.setPlanId("PLAN-2026-01");
|
||||
|
||||
try
|
||||
{
|
||||
terminalService.confirmTerminalReport(dto, null);
|
||||
fail("未携带操作员凭据必须被拒绝");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue(se.getMessage().contains("未通过身份认证"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
terminalService.confirmTerminalReport(dto, "anonymousUser");
|
||||
fail("匿名操作员必须被拒绝");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue(se.getMessage().contains("未通过身份认证"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全测试 4:studentId 仅作为业务检索实体标识,绝不能当作认证凭据。
|
||||
* 当外部未提供有效操作员凭据、仅在 DTO 中附带 studentId 时,拒绝操作。
|
||||
*/
|
||||
@Test
|
||||
public void testSecurityStudentIdIsNotAuthenticationCredential()
|
||||
{
|
||||
// 尝试以携带 studentId 作为自认证方式确认报到
|
||||
ReportTerminalConfirmDTO dto = new ReportTerminalConfirmDTO();
|
||||
dto.setStudentId("S101");
|
||||
dto.setPlanId("PLAN-2026-01");
|
||||
|
||||
try
|
||||
{
|
||||
// 操作员未经过服务端鉴权
|
||||
terminalService.confirmTerminalReport(dto, "");
|
||||
fail("仅有 studentId 而无有效操作员认证时必须拒绝确认报到");
|
||||
}
|
||||
catch (ServiceException se)
|
||||
{
|
||||
assertTrue(se.getMessage().contains("未通过身份认证"));
|
||||
}
|
||||
|
||||
// 携带合法已认证操作员后,正常完成业务流转
|
||||
ReportTerminalPreviewVO ok = terminalService.confirmTerminalReport(dto, "terminal_operator_admin");
|
||||
assertNotNull(ok);
|
||||
assertTrue(ok.getIsReported());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T createProxy(Class<T> clazz, InvocationHandler handler)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user