新增门禁:验证真实接口契约并落地暂行访问策略

This commit is contained in:
wangzhibiao committed 2026-09-16 14:58:11 +08:00
1 parent aa2e8a36de
commit e367ec1fe3
17 files changed
+806 -9

No files matched your search

@@ -0,0 +1,161 @@
package com.ruoyi.train.integration.dms.security;
import com.ruoyi.train.domain.TrainAccommodation;
import com.ruoyi.train.domain.TrainDormAuth;
import com.ruoyi.train.domain.TrainEnrollment;
import com.ruoyi.train.domain.TrainExternalOperation;
import com.ruoyi.train.domain.TrainExternalResourceMapping;
import com.ruoyi.train.integration.dms.orchestration.ReconciliationResult;
import com.ruoyi.web.controller.train.TrainDormAuthController;
import org.junit.Test;
import org.springframework.web.bind.annotation.GetMapping;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;
/**
* 验证 R.3 Provisional Business Policy V0 中的凭证一次性安全展示策略 (ADMIN_ONE_TIME_DISPLAY)
* 验证指标:
* 1. DB_PLAINTEXT_COUNT = 0
* 2. LOG_PLAINTEXT_EXPOSURE = 0
* 3. HISTORY_PLAINTEXT_EXPOSURE = 0
* 4. RETRIEVAL_ENDPOINT_COUNT = 0
*/
public class CredentialOneTimeDisplaySecurityTest {
/**
* 指标 1:DB_PLAINTEXT_COUNT = 0
* 证明所有核心业务表与操作流水的持久化领域实体类中,绝对没有包含明文密码的物理持久化字段
*/
@Test
public void testDbPlaintextCountIsZero() {
Class<?>[] persistentEntities = new Class<?>[]{
TrainDormAuth.class,
TrainExternalOperation.class,
TrainAccommodation.class,
TrainEnrollment.class,
TrainExternalResourceMapping.class
};
int dbPlaintextCount = 0;
List<String> suspiciousFields = new ArrayList<>();
for (Class<?> clazz : persistentEntities) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 排除 static 常量
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
String name = field.getName().toLowerCase();
if (name.contains("password") || name.contains("plaintext") || name.contains("secret")) {
dbPlaintextCount++;
suspiciousFields.add(clazz.getSimpleName() + "." + field.getName());
}
}
}
assertEquals("DB_PLAINTEXT_COUNT must be strictly 0; found: " + suspiciousFields, 0, dbPlaintextCount);
}
/**
* 指标 2:LOG_PLAINTEXT_EXPOSURE = 0
* 证明结果对象与关键传输类的 toString() 绝对不输出密码明文
*/
@Test
public void testLogPlaintextExposureIsZero() {
String sensitivePassword = "SecretPassword987654";
ReconciliationResult result = ReconciliationResult.grantRequired(1001L, 2001L, true, true, "Success");
result.setOneTimePlaintextCredential(sensitivePassword);
String str = result.toString();
assertFalse("ReconciliationResult.toString() must NOT contain plaintext password", str.contains(sensitivePassword));
assertTrue("ReconciliationResult.toString() should redact sensitive fields", str.contains("[PROTECTED]"));
// 验证 transient 关键字修饰
try {
Field credentialField = ReconciliationResult.class.getDeclaredField("oneTimePlaintextCredential");
assertTrue("oneTimePlaintextCredential must be declared transient", Modifier.isTransient(credentialField.getModifiers()));
} catch (NoSuchFieldException e) {
fail("Field oneTimePlaintextCredential must exist in ReconciliationResult");
}
}
/**
* 指标 3:HISTORY_PLAINTEXT_EXPOSURE = 0
* 证明外部操作流水 TrainExternalOperation 无论如何序列化或字段读取,均无法获得明文密码
*/
@Test
public void testHistoryPlaintextExposureIsZero() {
TrainExternalOperation op = new TrainExternalOperation();
op.setOperationId(9999L);
op.setExternalHardwareId("HW-TEST-01");
op.setExternalKeyId("KEY-12345");
op.setOperationStatus(TrainExternalOperation.STATUS_SUCCESS);
String opStr = op.toString();
assertFalse("TrainExternalOperation must not contain password token in string representation",
opStr.toLowerCase().contains("password"));
// 检查实体方法
Method[] methods = TrainExternalOperation.class.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName().toLowerCase();
assertFalse("TrainExternalOperation cannot have getPassword methods: " + method.getName(),
methodName.startsWith("get") && methodName.contains("password"));
}
}
/**
* 指标 4:RETRIEVAL_ENDPOINT_COUNT = 0
* 扫描门禁对外 HTTP 控制器,确认没有任何可以再次读取/回显已生成凭据明文的 GET 查询接口
*/
@Test
public void testRetrievalEndpointCountIsZero() {
Class<?> controllerClass = TrainDormAuthController.class;
int retrievalEndpointCount = 0;
List<String> leakEndpoints = new ArrayList<>();
for (Method method : controllerClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(GetMapping.class)) {
GetMapping getMapping = method.getAnnotation(GetMapping.class);
String[] paths = getMapping.value().length > 0 ? getMapping.value() : getMapping.path();
for (String path : paths) {
if (path.toLowerCase().contains("password") || path.toLowerCase().contains("credential")) {
retrievalEndpointCount++;
leakEndpoints.add(method.getName() + " (" + path + ")");
}
}
}
}
assertEquals("RETRIEVAL_ENDPOINT_COUNT must be strictly 0; found: " + leakEndpoints, 0, retrievalEndpointCount);
}
/**
* 验证 ADMIN_ONE_TIME_DISPLAY 闭环有效性:
* 瞬态凭据仅在创建成功当次内存中可读取一次,后续从持久化系统无法二次读取
*/
@Test
public void testAdminOneTimeDisplayBehavior() {
ReconciliationResult result = ReconciliationResult.grantRequired(1001L, 2001L, true, true, "Success");
result.setOneTimePlaintextCredential("123456");
// 本次可读取
assertEquals("123456", result.getOneTimePlaintextCredential());
// 模拟关闭/二次读取:持久化实体 TrainDormAuth 根本没有该属性
TrainDormAuth auth = new TrainDormAuth();
auth.setAuthId(1001L);
auth.setExternalAuthId("88001");
// 验证 TrainDormAuth 无法提供密码
assertNotNull(auth.getAuthId());
}
}