diff --git a/zdxt-common/zdxt-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java b/zdxt-common/zdxt-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java index 6e749783..19c40c5f 100644 --- a/zdxt-common/zdxt-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java +++ b/zdxt-common/zdxt-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java @@ -314,13 +314,16 @@ public class EncryptUtils { public static void main(String[] args) { - KeyPair keyPair = SecureUtil.generateKeyPair("RSA"); - byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); - byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); + EncryptUtils.decryptByAes("test", "zdxt2026PlatForm"); +// KeyPair keyPair = SecureUtil.generateKeyPair("RSA"); +// +// byte[] publicKeyBytes = keyPair.getPublic().getEncoded(); +// byte[] privateKeyBytes = keyPair.getPrivate().getEncoded(); - String publicKeyBase64 = Base64.encode(publicKeyBytes); - String privateKeyBase64 = Base64.encode(privateKeyBytes); +// +// String publicKeyBase64 = Base64.encode(publicKeyBytes); +// String privateKeyBase64 = Base64.encode(privateKeyBytes); // System.out.println("========== RSA 2048-bit 密钥对 =========="); // System.out.println(); diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java index fb716e27..6aeea328 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java @@ -26,11 +26,11 @@ public class IdCardCryptoUtil { String key = "VRJHzFqJpsSZhwnsKTWNspv_GZMxTWl-bgH6DyE9Ty0"; System.out.println("====================== 示例 ======================"); // 加密 - String source_dec = "15815555985"; - String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec); - System.out.println(source_dec+" 加密后 : "+enc); +// String source_dec = "15815555985"; +// String enc = IdCardCryptoUtil.symmetricEncryptIdCard(key, source_dec); +// System.out.println(source_dec+" 加密后 : "+enc); // 解密 - String source_enc = "wDLyWcBO/fIH2rJpSGN98A=="; + String source_enc = "d2H344KpIlRusePxEJbgQg=="; String dec = IdCardCryptoUtil.symmetricDecryptIdCard(key, source_enc); System.out.println(source_enc+" 解密后 : "+dec); @@ -42,16 +42,16 @@ public class IdCardCryptoUtil { // System.out.println(phone); // System.out.println(sfz); - - System.out.println("====================== 实用 ======================"); - String[] strings={ - "13360957126", - "16688211945", - "17339838670", - "18460319603", - "15626498490", - "18080147218", - }; +// +// System.out.println("====================== 实用 ======================"); +// String[] strings={ +// "13360957126", +// "16688211945", +// "17339838670", +// "18460319603", +// "15626498490", +// "18080147218", +// }; // 刘义 13410900924 19020722074 // 杜海荣 15019258592 19020722059 @@ -60,11 +60,11 @@ public class IdCardCryptoUtil { // 李大航 17799857610 19020722079 // String string=""; - for(String s:strings){ - String phoneJM=IdCardCryptoUtil.symmetricEncryptIdCard(key, s); - System.out.println(s+" 加密后: "+phoneJM); -// string=string+"'"+phoneJM+"',"; - } +// for(String s:strings){ +// String phoneJM=IdCardCryptoUtil.symmetricEncryptIdCard(key, s); +// System.out.println(s+" 加密后: "+phoneJM); +//// string=string+"'"+phoneJM+"',"; +// } // System.out.println(string); /* String[] strings={ diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnforcementRegistrationController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnforcementRegistrationController.java index e119a5e7..1fe84e3b 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnforcementRegistrationController.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnforcementRegistrationController.java @@ -28,6 +28,8 @@ import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.helper.DataPermissionHelper; @@ -88,6 +90,7 @@ public class AppEnforcementRegistrationController extends BaseController { @PostMapping("/add") @ResponseBody @RepeatSubmit() + @Log(title = "行政执法登记", businessType = BusinessType.INSERT) public R addSave(@RequestBody EnforcementRegistrationBo bo) { if (StringUtils.isNotEmpty(bo.getId())) { @@ -198,6 +201,7 @@ public class AppEnforcementRegistrationController extends BaseController { @PostMapping("/edit") @ResponseBody @RepeatSubmit() + @Log(title = "行政执法登记", businessType = BusinessType.UPDATE) public R editSave(@RequestBody EnforcementRegistrationBo bo) { int resNum = 0; @@ -234,6 +238,7 @@ public class AppEnforcementRegistrationController extends BaseController { @PostMapping("/editInfo") @ResponseBody @RepeatSubmit() + @Log(title = "行政执法登记", businessType = BusinessType.UPDATE) public R editInfo(@RequestBody EnforcementRegistrationBo bo) { bo.setIsSubmit(0L); diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java index dbcff6fb..0defd010 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java @@ -5,6 +5,8 @@ import com.zdxt.enforcementcode.domain.bo.NonEnforcementRegistrationBo; import com.zdxt.enforcementcode.domain.vo.NonEnforcementRegistrationVo; import com.zdxt.enforcementcode.service.INonEnforcementRegistrationService; import org.dromara.common.core.domain.R; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.web.core.BaseController; @@ -26,6 +28,7 @@ public class AppNonEnforcementRegistrationController extends BaseController { */ @PostMapping("/add") @ResponseBody + @Log(title = "非行政执法登记", businessType = BusinessType.INSERT) public R addSave(@RequestBody NonEnforcementRegistrationBo bo) { diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoIntegrityHmackJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoIntegrityHmackJob.java new file mode 100644 index 00000000..1cb1bf6b --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoIntegrityHmackJob.java @@ -0,0 +1,34 @@ + +package com.zdxt.enforcementcode.quartz.job; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.system.service.ICryptoSensitiveHsmService; +import org.dromara.system.service.IPasswordPartyService; +import org.springframework.stereotype.Component; + +/** + * 密码平台 + *

通过 Quartz 定时触发,

+ * + * @author zrb + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CryptoIntegrityHmackJob { + + private final ICryptoSensitiveHsmService cryptoSensitiveHsmService; + public void execute(){ + this.hmac(); + } + + public void hmac(){ + String [] tables = {"sys_user","sys_oper_log"}; + for (int i = 0; i < tables.length; i++) { + log.info("数据完整性校验表开始{}", tables[i]); + cryptoSensitiveHsmService.verifyIntegrityHash(tables[i]); + log.info("数据完整性校验表结束{}", tables[i]); + } + } +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveOperLogJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveOperLogJob.java new file mode 100644 index 00000000..6c39b0f2 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveOperLogJob.java @@ -0,0 +1,29 @@ +package com.zdxt.enforcementcode.quartz.job; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.system.service.ICryptoSensitiveHsmService; +import org.dromara.system.service.IPasswordPartyService; +import org.springframework.stereotype.Component; + +/** + * 密码平台 + *

通过 Quartz 定时触发,

+ * + * @author zrb + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CryptoSensitiveOperLogJob { + private final ICryptoSensitiveHsmService cryptoSensitiveHsmService; + public void execute(){ + this.syncIntegrityHashModifiedOperLogs(); + } + + public void syncIntegrityHashModifiedOperLogs(){ + log.info("同步操作日志密评开始"); + cryptoSensitiveHsmService.syncIntegrityHashModifiedOperLogs(); + log.info("同步操作日志密评结束"); + } +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveUserJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveUserJob.java new file mode 100644 index 00000000..1c8971e8 --- /dev/null +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveUserJob.java @@ -0,0 +1,30 @@ +package com.zdxt.enforcementcode.quartz.job; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.system.service.ICryptoSensitiveHsmService; +import org.springframework.stereotype.Component; + +/** + * + *

通过 Quartz 定时触发,

+ * + * @author zrb + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class CryptoSensitiveUserJob { + + private final ICryptoSensitiveHsmService cryptoSensitiveHsmService; + public void execute(){ + this.syncIntegrityHashModifiedUsers(); + } + + public void syncIntegrityHashModifiedUsers(){ + log.info("同步用户密评开始"); + cryptoSensitiveHsmService.syncIntegrityHashModifiedUsers(); + log.info("同步用户密评结束"); + } + +} diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynDeptInfoJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynDeptInfoJob.java index 44902a40..d52210bb 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynDeptInfoJob.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynDeptInfoJob.java @@ -35,8 +35,6 @@ public class SynDeptInfoJob { public static Map params = new HashMap<>(); - private static final Logger log = LoggerFactory.getLogger(SynDeptInfoJob.class); - public void run(){ this.deptInfo(); diff --git a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java index 688149b0..2947ef14 100644 --- a/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java +++ b/zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java @@ -36,7 +36,7 @@ public class SynUserInfoJob { private ISysUserService sysUserService; @Autowired - private ICryptoSensitiveHsmService ICryptoSensitiveHsmService; + private ICryptoSensitiveHsmService cryptoSensitiveHsmService; public void run() { this.userInfo(); @@ -140,14 +140,14 @@ public class SynUserInfoJob { // 密评规则:HSM 加密敏感字段存入 crypto_sensitive_data 表 String bizId = String.valueOf(userId); try { - String phoneCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", mobileNumber); - String idCardCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "certificateNumber", certificateNumber); + String phoneCipher = cryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", mobileNumber); + String idCardCipher = cryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "certificateNumber", certificateNumber); // 密码:仅新用户进行 HSM 加密,已有用户密码不动 String passwordCipher = null; SysUserBo updateBo = new SysUserBo(); updateBo.setUserId(userId); if (isNewUser) { - passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", sysUserBo.getPassword()); + passwordCipher = cryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", sysUserBo.getPassword()); updateBo.setPassword(passwordCipher); } updateBo.setPhonenumber(phoneCipher); @@ -164,7 +164,7 @@ public class SynUserInfoJob { } String phoneForHash = StringUtils.isNotEmpty(phoneCipher) ? phoneCipher : mobileNumber; String idCardForHash = StringUtils.isNotEmpty(idCardCipher) ? idCardCipher : certificateNumber; - ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId, + cryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId, "password,phonenumber,certificateNumber", pwdForHash, phoneForHash, idCardForHash); } catch (Exception e) { diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java index ea02eaaa..bb27f8fd 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java @@ -1,5 +1,7 @@ package org.dromara.system.domain.bean; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -70,4 +72,14 @@ public class CryptoIntegrityData { * 逻辑删除 */ private Integer isDeleted; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java index 5d204041..8b7dfe8c 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java @@ -1,9 +1,13 @@ package org.dromara.system.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.apache.ibatis.annotations.Param; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.system.domain.SysOperLog; import org.dromara.system.domain.vo.SysOperLogVo; +import java.util.Date; + /** * 操作日志 数据层 * @@ -11,4 +15,13 @@ import org.dromara.system.domain.vo.SysOperLogVo; */ public interface SysOperLogMapper extends BaseMapperPlus { + /** + * 分页查询指定操作时间范围内的操作日志记录(用于完整性摘要同步) + * + * @param page 分页参数 + * @param beginTime 开始时间(含) + * @param endTime 结束时间(含) + * @return 操作日志分页记录 + */ + Page selectByOperTimeRange(Page page, @Param("beginTime") Date beginTime, @Param("endTime") Date endTime); } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java index ebe31e25..1e11fec9 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java @@ -17,6 +17,7 @@ import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.vo.SysUserExportVo; import org.dromara.system.domain.vo.SysUserVo; +import java.util.Date; import java.util.List; /** @@ -156,4 +157,14 @@ public interface SysUserMapper extends BaseMapperPlus { List queryUserByIds(String[] ids); Page queryUsersByPage(@Param("page") Page page, @Param("user") SysUserBo user); + + /** + * 分页查询指定更新时间范围内的用户表记录(用于完整性摘要同步) + * + * @param page 分页参数 + * @param beginTime 开始时间(含) + * @param endTime 结束时间(含) + * @return 用户分页记录 + */ + Page selectModifiedUserByUpdateTime(Page page, @Param("beginTime") Date beginTime, @Param("endTime") Date endTime); } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java index 622a7d3c..dee53a5d 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java @@ -64,4 +64,40 @@ public interface ICryptoSensitiveHsmService { * @param plainValues 与 fieldList 顺序一致的明文值 */ void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues); + + /** + * 同步前一天修改过的 sys_user 数据完整性摘要 + *

+ * 查询 sys_user 表中 update_time 落在前一天范围内的记录, + * 以数据库中实际存储值(password、phonenumber、certificateNumber)重新计算 HMAC-SM3 摘要, + * 按 upsert 策略写入 crypto_integrity_data 表,供后续完整性校验比对。 + * + * @return 成功处理的记录数 + */ + int syncIntegrityHashModifiedUsers(); + + /** + * 同步前一天产生的 sys_oper_log 数据完整性摘要 + *

+ * 查询 sys_oper_log 表中 oper_time 落在前一天范围内的记录, + * 以数据库中实际存储值(business_type、method、oper_name、oper_param、oper_time) + * 重新计算 HMAC-SM3 摘要,按 upsert 策略写入 crypto_integrity_data 表,供后续完整性校验比对。 + * + * @return 成功处理的记录数 + */ + int syncIntegrityHashModifiedOperLogs(); + + /** + * 校验指定业务表的数据完整性摘要(HMAC-SM3) + *

+ * 查询 crypto_integrity_data 中 table_name 匹配且 generate_time 早于今天的基准摘要记录, + * 根据 table_name 与 biz_id 回查业务表当前数据重新计算摘要并比对: + * 一致则标记 verify_result=1(通过),不一致或业务数据缺失则标记 verify_result=2(篡改), + * 同时刷新 last_verify_time。 + * + * @param tableName 业务表名,当前支持 sys_user、sys_oper_log + * @return 完成校验的记录数 + */ + int verifyIntegrityHash(String tableName); + } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java index 1f07f4fb..dfcf86e3 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java @@ -1,12 +1,18 @@ package org.dromara.system.service.impl; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.common.mybatis.helper.DataPermissionHelper; +import org.dromara.common.core.exception.ServiceException; import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.system.domain.SysOperLog; +import org.dromara.system.domain.SysUser; import org.dromara.system.domain.bean.CryptoIntegrityData; import org.dromara.system.domain.bean.CryptoOperationAudit; import org.dromara.system.domain.bean.CryptoSensitiveData; @@ -14,8 +20,11 @@ import org.dromara.system.domain.vo.SymmetryEncryptVo; import org.dromara.system.mapper.CryptoIntegrityDataMapper; import org.dromara.system.mapper.CryptoOperationAuditMapper; import org.dromara.system.mapper.CryptoSensitiveDataMapper; +import org.dromara.system.mapper.SysOperLogMapper; +import org.dromara.system.mapper.SysUserMapper; import org.dromara.system.service.ICryptoSensitiveHsmService; import org.dromara.system.service.IPasswordPartyService; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -31,9 +40,12 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService private final CryptoSensitiveDataMapper cryptoSensitiveDataMapper; private final CryptoOperationAuditMapper cryptoOperationAuditMapper; private final CryptoIntegrityDataMapper cryptoIntegrityDataMapper; + private final SysUserMapper sysUserMapper; + private final SysOperLogMapper sysOperLogMapper; private static final String KEY_VERSION = "V1"; private static final String TABLE_SYS_USER = "sys_user"; + private static final String TABLE_SYS_OPER_LOG = "sys_oper_log"; @Override @Transactional(rollbackFor = Exception.class) @@ -111,40 +123,152 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService @Override @Transactional(rollbackFor = Exception.class) public void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < plainValues.length; i++) { - if (i > 0) sb.append(","); - sb.append(plainValues[i] != null ? plainValues[i] : ""); + try{ + String hmacHash = calculateHash(fieldList, plainValues); + + LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); + lqw.eq(CryptoIntegrityData::getTableName, tableName) + .eq(CryptoIntegrityData::getBizId, bizId) + .eq(CryptoIntegrityData::getIsDeleted, 0); + CryptoIntegrityData existing = cryptoIntegrityDataMapper.selectOne(lqw); + + if (existing != null) { + existing.setFieldList(fieldList); + existing.setSm3Hash(hmacHash); + existing.setHashAlg("SM3"); + Date date = new Date(); + existing.setGenerateTime(date); + existing.setLastVerifyTime(date); + existing.setVerifyResult(1); + cryptoIntegrityDataMapper.updateById(existing); + } else { + CryptoIntegrityData record = new CryptoIntegrityData(); + record.setTableName(tableName); + record.setBizId(bizId); + record.setFieldList(fieldList); + record.setSm3Hash(hmacHash); + record.setHashAlg("SM3"); + Date date = new Date(); + record.setGenerateTime(date); + record.setLastVerifyTime(date); + record.setVerifyResult(1); + record.setIsDeleted(0); + cryptoIntegrityDataMapper.insert(record); + } + } catch (Exception e) { + log.error("保存数据完整性摘要失败, tableName={}, bizId={}", tableName, bizId, e); } - String concatPlain = sb.toString(); - String inputHex = HexUtil.encodeHexStr(concatPlain.getBytes(StandardCharsets.UTF_8)); - String hmacHash = passwordPartyService.hmacWithIndex(inputHex); + } - LambdaQueryWrapper lqw = new LambdaQueryWrapper<>(); - lqw.eq(CryptoIntegrityData::getTableName, tableName) - .eq(CryptoIntegrityData::getBizId, bizId) - .eq(CryptoIntegrityData::getIsDeleted, 0); - CryptoIntegrityData existing = cryptoIntegrityDataMapper.selectOne(lqw); + @Override + public int verifyIntegrityHash(String tableName) { + if (!TABLE_SYS_USER.equals(tableName) && !TABLE_SYS_OPER_LOG.equals(tableName)) { + throw new ServiceException("暂不支持该业务表的完整性校验: " + tableName); + } + Date today = DateUtil.beginOfDay(new Date()); - if (existing != null) { - existing.setFieldList(fieldList); - existing.setSm3Hash(hmacHash); - existing.setHashAlg("SM3"); - existing.setGenerateTime(new Date()); - existing.setVerifyResult(0); - cryptoIntegrityDataMapper.updateById(existing); - } else { - CryptoIntegrityData record = new CryptoIntegrityData(); - record.setTableName(tableName); - record.setBizId(bizId); - record.setFieldList(fieldList); - record.setSm3Hash(hmacHash); - record.setHashAlg("SM3"); - record.setGenerateTime(new Date()); - record.setVerifyResult(0); - record.setIsDeleted(0); - cryptoIntegrityDataMapper.insert(record); + int processed = 0; + int passed = 0; + int tampered = 0; + long pageSize = 500L; + Page page = new Page<>(1, pageSize); + LambdaQueryWrapper lqw = new LambdaQueryWrapper() + .eq(CryptoIntegrityData::getTableName, tableName) + .eq(CryptoIntegrityData::getIsDeleted, 0) + .lt(CryptoIntegrityData::getGenerateTime, today); + while (true) { + Page result = cryptoIntegrityDataMapper.selectPage(page, lqw); + for (CryptoIntegrityData record : result.getRecords()) { + try { + boolean matched = TABLE_SYS_USER.equals(tableName) + ? verifySysUserIntegrity(record) + : verifySysOperLogIntegrity(record); + record.setVerifyResult(matched ? 1 : 2); + record.setLastVerifyTime(new Date()); + cryptoIntegrityDataMapper.updateById(record); + processed++; + if (matched) { + passed++; + } else { + tampered++; + } + } catch (Exception e) { + log.warn("完整性校验失败, tableName={}, bizId={}", record.getTableName(), record.getBizId(), e); + } + } + if (!result.hasNext()) { + break; + } + page.setCurrent(page.getCurrent() + 1); } + log.info("完整性校验完成, tableName={}, 处理={}, 通过={}, 异常={}", tableName, processed, passed, tampered); + return processed; + } + + @Override + public int syncIntegrityHashModifiedUsers() { + Date begin = DateUtil.beginOfDay(DateUtil.yesterday()); + Date end = DateUtil.endOfDay(DateUtil.yesterday()); + + int processed = 0; + long pageSize = 500L; + Page page = new Page<>(1, pageSize); + while (true) { + Page result = DataPermissionHelper.ignore( + () -> sysUserMapper.selectModifiedUserByUpdateTime(page, begin, end)); + for (SysUser user : result.getRecords()) { + try { + saveIntegrityHash(TABLE_SYS_USER, String.valueOf(user.getUserId()), + "password,phonenumber,certificateNumber", + user.getPassword() != null ? user.getPassword() : "", + user.getPhonenumber() != null ? user.getPhonenumber() : "", + user.getCertificateNumber() != null ? user.getCertificateNumber() : ""); + processed++; + } catch (Exception e) { + log.warn("同步前一天修改用户完整性摘要失败, userId={}", user.getUserId(), e); + } + } + if (!result.hasNext()) { + break; + } + page.setCurrent(page.getCurrent() + 1); + } + log.info("同步前一天修改用户完整性摘要完成, 处理数量={}", processed); + return processed; + } + + @Override + public int syncIntegrityHashModifiedOperLogs() { + Date begin = DateUtil.beginOfDay(DateUtil.yesterday()); + Date end = DateUtil.endOfDay(DateUtil.yesterday()); + + int processed = 0; + long pageSize = 500L; + Page page = new Page<>(1, pageSize); + while (true) { + Page result = DataPermissionHelper.ignore( + () -> sysOperLogMapper.selectByOperTimeRange(page, begin, end)); + for (SysOperLog operLog : result.getRecords()) { + try { + saveIntegrityHash(TABLE_SYS_OPER_LOG, String.valueOf(operLog.getOperId()), + "business_type,method,oper_name,oper_param,oper_time", + toStr(operLog.getBusinessType()), + toStr(operLog.getMethod()), + toStr(operLog.getOperName()), + toStr(operLog.getOperParam()), + toStr(operLog.getOperTime())); + processed++; + } catch (Exception e) { + log.warn("同步前一天操作日志完整性摘要失败, operId={}", operLog.getOperId(), e); + } + } + if (!result.hasNext()) { + break; + } + page.setCurrent(page.getCurrent() + 1); + } + log.info("同步前一天操作日志完整性摘要完成, 处理数量={}", processed); + return processed; } private CryptoSensitiveData queryActiveRecord(String tableName, String bizId, String fieldCode) { @@ -183,6 +307,16 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService cryptoOperationAuditMapper.insert(audit); } + private String calculateHash(String fieldList, String... plainValues) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < plainValues.length; i++) { + if (i > 0) sb.append(","); + sb.append(plainValues[i] != null ? plainValues[i] : ""); + } + String inputHex = HexUtil.encodeHexStr(sb.toString().getBytes(StandardCharsets.UTF_8)); + return passwordPartyService.hmacWithIndex(inputHex); + } + private static byte[] hexToBytes(String hex) { int len = hex.length(); byte[] data = new byte[len / 2]; @@ -192,4 +326,77 @@ public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService } return data; } + + /** + * 校验 sys_user 表数据完整性 + *

+ * 根据基准摘要记录的 biz_id 回查 sys_user 当前数据, + * 按 field_list 解析参与字段(password、phonenumber、certificateNumber), + * 重新计算 HMAC-SM3 摘要并与基准 sm3_hash 比对。 + * + * @param record 基准摘要记录 + * @return true=校验通过,false=数据缺失或摘要不一致(疑似篡改) + */ + private boolean verifySysUserIntegrity(CryptoIntegrityData record) { + SysUser user = DataPermissionHelper.ignore( + () -> sysUserMapper.selectById(Long.valueOf(record.getBizId()))); + if (user == null) { + log.warn("完整性校验: sys_user 数据不存在, bizId={}", record.getBizId()); + return false; + } + String[] fields = record.getFieldList().split(","); + String[] values = new String[fields.length]; + for (int i = 0; i < fields.length; i++) { + String field = fields[i].trim(); + switch (field) { + case "password" -> values[i] = user.getPassword() != null ? user.getPassword() : ""; + case "phonenumber" -> values[i] = user.getPhonenumber() != null ? user.getPhonenumber() : ""; + case "certificateNumber" -> values[i] = user.getCertificateNumber() != null ? user.getCertificateNumber() : ""; + default -> throw new ServiceException("不支持的完整性校验字段: " + field); + } + } + String currentHash = calculateHash(record.getFieldList(), values); + boolean matched = currentHash.equals(record.getSm3Hash()); + if (!matched) { + log.warn("数据完整性校验未通过, 疑似篡改, tableName={}, bizId={}", record.getTableName(), record.getBizId()); + } + return matched; + } + + /** + * 校验 sys_oper_log 表数据完整性 + *

+ * 根据基准摘要记录的 biz_id 回查 sys_oper_log 当前数据, + * 固定按 business_type、method、oper_name、oper_param、oper_time 五个字段 + * 重新计算 HMAC-SM3 摘要并与基准 sm3_hash 比对。 + * 非字符串字段统一按 String.valueOf 转换,与保存摘要时的取值口径保持一致。 + * + * @param record 基准摘要记录 + * @return true=校验通过,false=数据缺失或摘要不一致(疑似篡改) + */ + private boolean verifySysOperLogIntegrity(CryptoIntegrityData record) { + SysOperLog operLog = DataPermissionHelper.ignore( + () -> sysOperLogMapper.selectById(Long.valueOf(record.getBizId()))); + if (operLog == null) { + log.warn("完整性校验: sys_oper_log 数据不存在, bizId={}", record.getBizId()); + return false; + } + String[] values = new String[]{ + toStr(operLog.getBusinessType()), + toStr(operLog.getMethod()), + toStr(operLog.getOperName()), + toStr(operLog.getOperParam()), + toStr(operLog.getOperTime()) + }; + String currentHash = calculateHash(record.getFieldList(), values); + boolean matched = currentHash.equals(record.getSm3Hash()); + if (!matched) { + log.warn("数据完整性校验未通过, 疑似篡改, tableName={}, bizId={}", record.getTableName(), record.getBizId()); + } + return matched; + } + + private static String toStr(Object value) { + return value != null ? String.valueOf(value) : ""; + } } diff --git a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java index 9399c901..ea323fe1 100644 --- a/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java +++ b/zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.util.ArrayUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.ip.AddressUtils; @@ -14,6 +15,7 @@ import org.dromara.system.domain.SysOperLog; import org.dromara.system.domain.bo.SysOperLogBo; import org.dromara.system.domain.vo.SysOperLogVo; import org.dromara.system.mapper.SysOperLogMapper; +import org.dromara.system.service.ICryptoSensitiveHsmService; import org.dromara.system.service.ISysOperLogService; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; @@ -29,11 +31,13 @@ import java.util.Map; * * @author Lion Li */ +@Slf4j @RequiredArgsConstructor @Service public class SysOperLogServiceImpl implements ISysOperLogService { private final SysOperLogMapper baseMapper; + private final ICryptoSensitiveHsmService cryptoSensitiveHsmService; /** * 操作日志记录 @@ -95,6 +99,18 @@ public class SysOperLogServiceImpl implements ISysOperLogService { SysOperLog operLog = MapstructUtils.convert(bo, SysOperLog.class); operLog.setOperTime(new Date()); baseMapper.insert(operLog); + + try { + cryptoSensitiveHsmService.saveIntegrityHash("sys_oper_log", String.valueOf(operLog.getOperId()), + "business_type,method,oper_name,oper_param,oper_time", + String.valueOf(operLog.getBusinessType()), + operLog.getMethod() != null ? operLog.getMethod() : "", + operLog.getOperName() != null ? operLog.getOperName() : "", + operLog.getOperParam() != null ? operLog.getOperParam() : "", + String.valueOf(operLog.getOperTime())); + } catch (Exception e) { + log.warn("操作日志完整性摘要保存失败, operId={}", operLog.getOperId(), e); + } } /** diff --git a/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml index 5ef14eee..93392a07 100644 --- a/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml +++ b/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -4,4 +4,11 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> + + diff --git a/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml b/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml index 6753eb44..ec19bfed 100644 --- a/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml +++ b/zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -237,4 +237,12 @@ #{id} + +