Browse Source

密评优化

dev-V1.10.4
vincent 6 hours ago
parent
commit
efcfc62a7b
  1. 33
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java
  2. 15
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java
  3. 105
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java

33
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/web/supervision/CommonController.java

@ -154,4 +154,37 @@ public class CommonController extends BaseController {
return R.ok("同步用户密评成功"); return R.ok("同步用户密评成功");
} }
/**
* 数据完整性校验
* @return
*/
@GetMapping("/verifyUserInfo")
public R<Map<String, Integer>> verifyUserInfo() {
return R.ok("同步用户密评成功");
}
/**
* 校验指定登录账号的数据完整性(HMAC-SM3 摘要与基线比对)
* <p>回查 sys_user 当前 password、phonenumber、certificateNumber 实际存储值,
* 重算 HMAC-SM3 摘要与 crypto_integrity_data 基线比对:
* verifyResult=1 通过、2 疑似篡改、3 跳过(用户不存在/已删除)、4 校验异常(非篡改)</p>
*
* @param loginName 登录账号
* @return 校验结果(userId、passed、verifyResult、fieldList、message)
*/
@GetMapping("/verifyUserDataConsistency")
public R<Map<String, Object>> verifyUserDataConsistency(
@NotBlank(message = "登录账号不能为空") @RequestParam String loginName) {
log.info("指定账号数据完整性校验开始, loginName={}", loginName);
Map<String, Object> result = DataPermissionHelper.ignore(
() -> cryptoSensitiveHsmService.verifyUserDataConsistency(loginName));
log.info("指定账号数据完整性校验结束, loginName={}, verifyResult={}, passed={}",
loginName, result.get("verifyResult"), result.get("passed"));
return R.ok("校验完成", result);
}
} }

15
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java

@ -231,4 +231,19 @@ public interface ICryptoSensitiveHsmService {
*/ */
Map<String, Integer> initSysUserIntegrityData(String loginName); Map<String, Integer> initSysUserIntegrityData(String loginName);
/**
* 校验指定登录账号的数据完整性(HMAC-SM3 摘要与基线比对)
* <p>
* 以 crypto_integrity_data 中该用户的基准摘要为基线, 回查 sys_user 当前
* password、phonenumber、certificateNumber 实际存储值重算 HMAC-SM3 摘要比对:
* 一致标记 verify_result=1(通过), 不一致标记 verify_result=2(疑似篡改),
* 用户已删除标记 verify_result=3(跳过), 校验过程异常标记 verify_result=4(校验异常, 非篡改),
* 同时刷新 last_verify_time。
* 兼容存量旧口径基线(纯逗号拼接)匹配后平滑迁移为新口径。
*
* @param loginName 登录账号(必填)
* @return 校验结果: userId、passed(是否通过)、verifyResult(1/2/3/4)、fieldList、message
*/
Map<String, Object> verifyUserDataConsistency(String loginName);
} }

105
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java

@ -251,6 +251,14 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService
return sysUserMapper.update(null, uw) > 0; return sysUserMapper.update(null, uw) > 0;
} }
/**
* 解密获取明文(SM4-CBC 对称解密)
*
* @param tableName 业务表名
* @param bizId 业务记录主键
* @param fieldCode 字段编码
* @return 明文值,不存在则返回 null
*/
@Override @Override
public String decryptField(String tableName, String bizId, String fieldCode) { public String decryptField(String tableName, String bizId, String fieldCode) {
CryptoSensitiveData record = queryActiveRecord(tableName, bizId, fieldCode); CryptoSensitiveData record = queryActiveRecord(tableName, bizId, fieldCode);
@ -592,6 +600,103 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService
return stats; return stats;
} }
@Override
public Map<String, Object> verifyUserDataConsistency(String loginName) {
if (StrUtil.isBlank(loginName)) {
throw new ServiceException("登录账号不能为空");
}
SysUser user = DataPermissionHelper.ignore(() -> sysUserMapper.selectOne(
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getLoginName, loginName)
.eq(SysUser::getDelFlag, "0")
.last("limit 1")));
if (user == null) {
throw new ServiceException("用户不存在: " + loginName);
}
Map<String, Object> result = new HashMap<>();
result.put("loginName", loginName);
result.put("userId", user.getUserId());
boolean allConsistent = true;
for (String fieldCode : new String[]{"password", "phonenumber", "certificateNumber"}) {
Map<String, Object> item = verifyOneFieldConsistency(user, fieldCode);
result.put(fieldCode, item);
if (!Boolean.TRUE.equals(item.get("consistent"))) {
allConsistent = false;
}
}
result.put("consistent", allConsistent);
log.info("指定账号敏感数据一致性校验完成, loginName={}, userId={}, 一致={}",
loginName, user.getUserId(), allConsistent);
return result;
}
/**
* 单字段一致性校验(三口径):
* (1) 业务列值 == crypto_sensitive_data.cipher_text;
* (2) 备份字段 crypto_remark_* == crypto_sensitive_data.field_value(原逻辑值);
* (3) HSM 解密原逻辑值归一化到明文 == 备份字段解析明文(password 原逻辑值即 MD5, 与备份直接比对)
*/
private Map<String, Object> verifyOneFieldConsistency(SysUser user, String fieldCode) {
Map<String, Object> item = new HashMap<>();
String bizId = String.valueOf(user.getUserId());
String storedValue = switch (fieldCode) {
case "password" -> user.getPassword();
case "phonenumber" -> user.getPhonenumber();
case "certificateNumber" -> user.getCertificateNumber();
default -> null;
};
String remarkValue = getRemarkValue(user, fieldCode);
CryptoSensitiveData record = queryActiveRecord(TABLE_SYS_USER, bizId, fieldCode);
if (StrUtil.isBlank(storedValue) && record == null) {
item.put("status", "skip");
item.put("consistent", true);
item.put("message", "字段为空且无密评记录, 跳过");
return item;
}
if (record == null || StrUtil.isBlank(record.getCipherText())) {
item.put("status", "missingRecord");
item.put("consistent", false);
item.put("message", "crypto_sensitive_data 无活跃密文记录");
log.error("一致性校验: 密评记录缺失, userId={}, fieldCode={}", bizId, fieldCode);
return item;
}
boolean cipherMatch = StrUtil.isNotBlank(storedValue) && storedValue.equals(record.getCipherText());
item.put("cipherMatch", cipherMatch);
boolean backupMatch = StrUtil.equals(remarkValue, record.getFieldValue());
item.put("backupMatch", backupMatch);
boolean plainMatch;
try {
String originalForm = decryptField(TABLE_SYS_USER, bizId, fieldCode);
if (StrUtil.isBlank(originalForm)) {
plainMatch = false;
item.put("message", "HSM 解密失败");
} else if ("password".equals(fieldCode)) {
plainMatch = originalForm.equals(remarkValue);
} else {
String hsmPlain = normalizeToPlain(fieldCode, originalForm);
String backupPlain = StrUtil.isBlank(remarkValue) ? null : normalizeToPlain(fieldCode, remarkValue);
plainMatch = StrUtil.isNotBlank(hsmPlain) && hsmPlain.equals(backupPlain);
}
} catch (Exception e) {
// 按规范: HSM 调用失败记录日志不中断流程, 标记校验异常(非篡改)
log.error("一致性校验 HSM 解密异常, userId={}, fieldCode={}", bizId, fieldCode, e);
plainMatch = false;
item.put("status", "hsmError");
item.put("message", "HSM 解密异常: " + e.getMessage());
}
item.put("plainMatch", plainMatch);
boolean consistent = cipherMatch && backupMatch && plainMatch;
item.put("consistent", consistent);
if (!consistent) {
item.put("status", StrUtil.isBlank((String) item.get("status")) ? "inconsistent" : item.get("status"));
log.error("敏感数据一致性校验未通过, userId={}, cipherMatch={}, backupMatch={}, plainMatch={}",
bizId, cipherMatch, backupMatch, plainMatch);
} else {
item.put("status", "ok");
}
return item;
}
/** /**
* 查询指定业务记录是否已存在完整性摘要 * 查询指定业务记录是否已存在完整性摘要
*/ */

Loading…
Cancel
Save