新增门禁:验证真实接口契约并落地暂行访问策略
This commit is contained in:
1 parent
aa2e8a36de
commit
e367ec1fe3
17 files changed
+806
-9
No files matched your search
+161
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user