Compare commits

...

23 Commits

  1. 67
      script/sql/vincent/V1.0.9_密评.sql
  2. 13
      zdxt-common/zdxt-common-encrypt/src/main/java/org/dromara/common/encrypt/utils/EncryptUtils.java
  3. 8
      zdxt-common/zdxt-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
  4. 101
      zdxt-modules/zdxt-enforcement-code-common/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java
  5. 4
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/BusinessDataSyncScheduler.java
  6. 38
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/IdCardCryptoUtil.java
  7. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/QRCodeUtil.java
  8. 27
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/LoginController.java
  9. 4
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfLoginController.java
  10. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfSystemController.java
  11. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnforcementRegistrationController.java
  12. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java
  13. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/supervision/AppSupLoginController.java
  14. 205
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bean/GdsztkY2010Dhr.java
  15. 148
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bean/GdsztkY2020Jb2.java
  16. 233
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/GdsztkY2010DhrVo.java
  17. 165
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/GdsztkY2020Jb2Vo.java
  18. 4
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/passwordparty/KeyPairVo.java
  19. 23
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/GdsztkY2010DhrMapper.java
  20. 23
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/GdsztkY2020Jb2Mapper.java
  21. 34
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoIntegrityHmackJob.java
  22. 29
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveOperLogJob.java
  23. 30
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/CryptoSensitiveUserJob.java
  24. 34
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SrzUserOriginalDataJob.java
  25. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynDeptInfoJob.java
  26. 178
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java
  27. 15
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IGdsztkY2020Jb2Service.java
  28. 6
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/enterprise/impl/AppEnterpriseComplaintServiceImpl.java
  29. 2
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/enterprise/impl/AppEnterpriseLoginServiceImpl.java
  30. 40
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/GdsztkY2020Jb2ServiceSserviceImpl.java
  31. 1
      zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/SrzUserServiceImpl.java
  32. 48
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/GdsztkY2010DhrMapper.xml
  33. 38
      zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/GdsztkY2020Jb2Mapper.xml
  34. 5
      zdxt-modules/zdxt-system/pom.xml
  35. 32
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/config/PasswordPartyConfig.java
  36. 90
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/CryptoSensitiveController.java
  37. 75
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java
  38. 132
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/SysUserController.java
  39. 18
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/SysUser.java
  40. 85
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java
  41. 78
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoOperationAudit.java
  42. 87
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoSensitiveData.java
  43. 17
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java
  44. 62
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoIntegrityDataVo.java
  45. 54
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoOperationAuditVo.java
  46. 72
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoSensitiveDataVo.java
  47. 18
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/SymmetryEncryptVo.java
  48. 24
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java
  49. 71
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java
  50. 9
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoIntegrityDataMapper.java
  51. 10
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoOperationAuditMapper.java
  52. 10
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoSensitiveDataMapper.java
  53. 13
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysOperLogMapper.java
  54. 11
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/SysUserMapper.java
  55. 136
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ICryptoSensitiveHsmService.java
  56. 81
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/IPasswordPartyService.java
  57. 8
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java
  58. 641
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/CryptoSensitiveHsmServiceImpl.java
  59. 267
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/PasswordPartyServiceImpl.java
  60. 1
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysJobServiceImpl.java
  61. 16
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysOperLogServiceImpl.java
  62. 19
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java
  63. 6
      zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java
  64. 7
      zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml
  65. 8
      zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml
  66. 17
      zdxt-web-server/zdxt-admin-web-server/src/main/java/org/dromara/web/service/SysRegisterService.java
  67. 16
      zdxt-web-server/zdxt-admin-web-server/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java
  68. 5
      zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-dev.yml
  69. 8
      zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-prod.yml
  70. 2
      zdxt-web-server/zdxt-admin-web-server/src/main/resources/businessdatasyn/sys_user.json

67
script/sql/vincent/V1.0.9_密评.sql

@ -0,0 +1,67 @@
-- 通用密评敏感字段密文存储表
CREATE TABLE crypto_sensitive_data (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
table_name VARCHAR(64) NOT NULL COMMENT '业务表名,如 sys_user',
biz_id VARCHAR(128) NOT NULL COMMENT '业务表主键ID,字符串兼容数字/UUID主键',
field_code VARCHAR(64) NOT NULL COMMENT '业务字段编码,username/id_card/phone/password',
field_value VARCHAR(512) DEFAULT NULL COMMENT '字段原始值',
cipher_text TEXT COMMENT 'SM4-CBC密文Base64;password单向哈希场景此字段为空',
field_iv VARCHAR(64) COMMENT 'SM4-CBC初始化向量,16字节随机值Base64;password此字段为空',
plain_hash VARCHAR(128) COMMENT '明文SM3哈希,仅用于等值查询,不可逆',
field_salt VARCHAR(64) COMMENT '哈希盐值,仅password单向哈希字段使用',
encrypt_alg VARCHAR(32) NOT NULL DEFAULT 'SM4-CBC' COMMENT '加密算法:SM4-CBC / SM3',
key_version VARCHAR(32) NOT NULL COMMENT '密钥版本号,用于密钥轮换,密评必查',
is_active TINYINT NOT NULL DEFAULT 1 COMMENT '是否为当前有效密文:1有效,0历史旧密文(密钥轮换保留)',
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0未删除,1已删除',
create_by BIGINT COMMENT '创建人ID',
update_by BIGINT COMMENT '更新人ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY uk_table_biz_field (table_name, biz_id, field_code, is_active, is_deleted),
KEY idx_table_bizid (table_name, biz_id),
KEY idx_plain_hash (plain_hash),
KEY idx_key_version (key_version),
KEY idx_field_code (field_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='密评敏感字段密文存储表';
-- 密评敏感操作审计表(记录解密、新增、修改、密钥轮换,满足密码应用审计)
CREATE TABLE crypto_operation_audit (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
sensitive_data_id BIGINT COMMENT '关联 crypto_sensitive_data 主键ID',
table_name VARCHAR(64) NOT NULL COMMENT '业务表名',
biz_id VARCHAR(128) NOT NULL COMMENT '业务记录主键',
field_code VARCHAR(64) NOT NULL COMMENT '敏感字段编码',
operate_type TINYINT NOT NULL COMMENT '操作类型:1新增密文 2解密查看 3修改密文 4密钥轮换',
operator_id BIGINT COMMENT '操作人ID(系统用户ID)',
operator_ip VARCHAR(64) COMMENT '操作客户端IP地址',
operate_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '操作发生时间',
remark TEXT COMMENT '操作备注信息',
is_deleted TINYINT DEFAULT 0 COMMENT '逻辑删除',
KEY idx_sensitive_data_id (sensitive_data_id),
KEY idx_table_bizid (table_name, biz_id),
KEY idx_operate_time (operate_time),
KEY idx_operator_id (operator_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='密评敏感字段操作审计表';
-- 通用数据完整性校验表(密评 GB/T 39786 数据完整性)
CREATE TABLE crypto_integrity_data (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
table_name VARCHAR(64) NOT NULL COMMENT '业务表名,如 sys_user',
biz_id VARCHAR(128) NOT NULL COMMENT '业务记录主键,字符串兼容数字/UUID',
field_list TEXT NOT NULL COMMENT '参与完整性摘要的字段列表,逗号分隔:username,id_card,phone',
sm3_hash VARCHAR(128) NOT NULL COMMENT '业务字段拼接明文的SM3摘要值,用于完整性校验',
hash_alg VARCHAR(32) NOT NULL DEFAULT 'SM3' COMMENT '完整性哈希算法:SM3',
generate_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '摘要生成时间',
last_verify_time DATETIME NULL COMMENT '最近一次完整性校验时间',
verify_result TINYINT NULL COMMENT '校验结果:0未校验 1校验通过 2校验失败(篡改)',
is_deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除',
create_by BIGINT COMMENT '创建人ID',
update_by BIGINT COMMENT '更新人ID',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
-- 唯一约束:一张业务表,一条业务记录,仅保留一条有效完整性摘要
UNIQUE KEY uk_table_biz (table_name, biz_id, is_deleted),
KEY idx_table_bizid (table_name, biz_id),
KEY idx_verify_result (verify_result),
KEY idx_generate_time (generate_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据完整性校验表';

13
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();

8
zdxt-common/zdxt-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java

@ -76,14 +76,12 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
public void updateFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
// 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
Date current = new Date();
baseEntity.setUpdateTime(current);
// 获取当前登录用户的ID,并填充更新人信息
Long userId = LoginHelper.getUserId();
if (ObjectUtil.isNotNull(userId)) {
baseEntity.setUpdateBy(userId);
LoginUser loginUser = getLoginUser();
if (ObjectUtil.isNotNull(loginUser)) {
baseEntity.setUpdateBy(loginUser.getUserId());
} else {
baseEntity.setUpdateBy(DEFAULT_USER_ID);
}

101
zdxt-modules/zdxt-enforcement-code-common/src/main/java/com/zdxt/enforcementcode/service/impl/EfcodeVirtualUnitServiceImpl.java

@ -16,6 +16,8 @@ import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zdxt.enforcementcode.domain.bo.EfcodeVirtualUnitBo;
import com.zdxt.enforcementcode.domain.bo.UnitUserBo;
@ -56,6 +58,9 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
private final SysUserMapper sysUserMapper;
private final ISysDeptService sysDeptService;
@Autowired
private ICryptoSensitiveHsmService ICryptoSensitiveHsmService;
/**
* 查询虚拟单位树
*
@ -170,7 +175,7 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
* 保存前的数据校验
*/
private void validEntityBeforeSave(EfcodeVirtualUnit entity){
//TODO 做一些数据校验,如唯一约束
}
/**
@ -183,7 +188,7 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
@Override
public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
// 删除前查询记录,获取 srzOrganizationId 用于清除 sys_dept.zone
List<EfcodeVirtualUnit> units = baseMapper.selectBatchIds(ids);
@ -367,7 +372,6 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
*/
@Override
public void addUser(UnitUserBo bo) {
// 校验登录账号唯一性
long count = sysUserMapper.selectCount(
Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLoginName, bo.getLoginName())
);
@ -385,13 +389,28 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
user.setNickName(bo.getFullname());
user.setOrganizationId(bo.getDeptId());
user.setMobileNumber(null);
// 生成6位十六进制salt并使用 MD5(loginName + password + salt) 加密
String salt = cn.hutool.core.util.RandomUtil.randomString("0123456789abcdef", 6);
user.setSalt(salt);
user.setPassword(DigestUtil.md5Hex(bo.getLoginName() + bo.getPassword() + salt));
String md5Password = DigestUtil.md5Hex(bo.getLoginName() + bo.getPassword() + salt);
user.setPassword(md5Password);
user.setStatus(bo.getStatus() != null ? bo.getStatus() : "0");
user.setUserSource(10);
sysUserMapper.insert(user);
try {
String bizId = String.valueOf(user.getUserId());
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", md5Password);
if (StringUtils.isNotBlank(passwordCipher)) {
sysUserMapper.update(null, Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, user.getUserId())
.set(SysUser::getPassword, passwordCipher));
}
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
passwordCipher != null ? passwordCipher : md5Password, "", "");
} catch (Exception e) {
log.warn("HSM加密新增用户密码失败, userId={}", user.getUserId(), e);
}
}
/**
@ -409,7 +428,6 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
if (user.getUserSource() == null || user.getUserSource() != 10) {
throw new ServiceException("仅允许编辑本系统新增的用户");
}
// 若修改了登录账号,校验唯一性
if (StringUtils.isNotBlank(bo.getLoginName()) && !bo.getLoginName().equals(user.getLoginName())) {
long count = sysUserMapper.selectCount(
Wrappers.<SysUser>lambdaQuery().eq(SysUser::getLoginName, bo.getLoginName())
@ -418,6 +436,10 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
throw new ServiceException("登录账号'" + bo.getLoginName() + "'已存在");
}
}
String md5Password = null;
String phoneCipher = null;
LambdaUpdateWrapper<SysUser> uw = Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, bo.getUserId())
.set(StringUtils.isNotBlank(bo.getDeptId()), SysUser::getDeptId, bo.getDeptId())
@ -425,14 +447,50 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
.set(StringUtils.isNotBlank(bo.getFullname()), SysUser::getNickName, bo.getFullname())
.set(StringUtils.isNotBlank(bo.getLoginName()), SysUser::getLoginName, bo.getLoginName())
.set(StringUtils.isNotBlank(bo.getLoginName()), SysUser::getUserName, bo.getLoginName())
.set(bo.getMobileNumber() != null, SysUser::getMobileNumber, bo.getMobileNumber())
.set(StringUtils.isNotBlank(bo.getStatus()), SysUser::getStatus, bo.getStatus());
if (StringUtils.isNotBlank(bo.getPassword())) {
String salt = user.getSalt();
String loginName = StringUtils.isNotBlank(bo.getLoginName()) ? bo.getLoginName() : user.getLoginName();
uw.set(SysUser::getPassword, DigestUtil.md5Hex(loginName + bo.getPassword() + salt));
md5Password = DigestUtil.md5Hex(loginName + bo.getPassword() + salt);
uw.set(SysUser::getPassword, md5Password);
}
if (bo.getMobileNumber() != null) {
try {
String bizId = String.valueOf(bo.getUserId());
phoneCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", bo.getMobileNumber());
} catch (Exception e) {
log.warn("HSM加密手机号失败, userId={}", bo.getUserId(), e);
}
if (StringUtils.isNotBlank(phoneCipher)) {
uw.set(SysUser::getMobileNumber, phoneCipher);
} else {
uw.set(SysUser::getMobileNumber, bo.getMobileNumber());
}
}
sysUserMapper.update(null, uw);
try {
String bizId = String.valueOf(bo.getUserId());
if (StringUtils.isNotBlank(bo.getPassword()) && md5Password != null) {
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", md5Password);
if (StringUtils.isNotBlank(passwordCipher)) {
sysUserMapper.update(null, Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, bo.getUserId())
.set(SysUser::getPassword, passwordCipher));
}
}
SysUser updatedUser = sysUserMapper.selectById(bo.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
phoneCipher != null ? phoneCipher : (updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : ""),
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", bo.getUserId(), e);
}
}
/**
@ -453,7 +511,6 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
if (user == null) {
throw new ServiceException("用户不存在");
}
// salt 为空时生成新的 salt,保证与登录校验方式一致
String salt = user.getSalt();
LambdaUpdateWrapper<SysUser> uw = Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, userId);
@ -461,8 +518,32 @@ public class EfcodeVirtualUnitServiceImpl implements IEfcodeVirtualUnitService {
salt = cn.hutool.core.util.RandomUtil.randomString("0123456789abcdef", 6);
uw.set(SysUser::getSalt, salt);
}
uw.set(SysUser::getPassword, DigestUtil.md5Hex(user.getLoginName() + password + salt));
String md5Password = DigestUtil.md5Hex(user.getLoginName() + password + salt);
uw.set(SysUser::getPassword, md5Password);
sysUserMapper.update(null, uw);
try {
String bizId = String.valueOf(userId);
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", md5Password);
if (StringUtils.isNotBlank(passwordCipher)) {
sysUserMapper.update(null, Wrappers.<SysUser>lambdaUpdate()
.eq(SysUser::getUserId, userId)
.set(SysUser::getPassword, passwordCipher));
}
} catch (Exception e) {
log.warn("HSM加密重置密码失败, userId={}", userId, e);
}
try {
String bizId = String.valueOf(userId);
SysUser updatedUser = sysUserMapper.selectById(userId);
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", userId, e);
}
}

4
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/businessdatasyn/executor/BusinessDataSyncScheduler.java

@ -59,6 +59,10 @@ public class BusinessDataSyncScheduler {
log.warn("[BizSync] 配置不存在: {}", configKey);
return;
}
if (!config.isEnabled()) {
log.warn("[BizSync] 配置已禁用, 跳过手动同步: {}", configKey);
return;
}
tableSyncExecutor.runOneTable(config, false);
}

38
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={

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/common/util/QRCodeUtil.java

@ -357,11 +357,9 @@ public class QRCodeUtil {
return base;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (WriterException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
return null;

27
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/LoginController.java

@ -4,15 +4,15 @@ import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.crypto.digest.DigestUtil;
import com.zdxt.enforcementcode.common.commonenum.PlatformTypeEnums;
import com.zdxt.enforcementcode.common.config.RuoYiConfig;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
/**
@ -39,7 +39,11 @@ public abstract class LoginController {
protected static final long APP_TOKEN_ACTIVE_TIMEOUT = 86400;
@Autowired
private com.zdxt.enforcementcode.common.config.RuoYiConfig ruoYiConfig;
private RuoYiConfig ruoYiConfig;
@Autowired
private ICryptoSensitiveHsmService ICryptoSensitiveHsmService;
/**
* 校验登录参数非空
@ -78,6 +82,23 @@ public abstract class LoginController {
return md5Password.equals(storedPassword);
}
/**
* HSM优先的密码校验(App端登录使用)
* 优先从 crypto_sensitive_data 解密比对,降级使用 MD5+salt
*/
protected boolean verifyPasswordWithHsm(Long userId, String loginName, String decryptedPassword, String salt, String storedPassword) {
String bizId = String.valueOf(userId);
try {
if (ICryptoSensitiveHsmService.verifyPassword("sys_user", bizId, decryptedPassword)) {
return true;
}
} catch (Exception e) {
log.warn("HSM密码校验失败, userId={}, 降级使用MD5校验", userId);
}
String md5Password = DigestUtil.md5Hex(loginName + decryptedPassword + salt);
return md5Password.equals(storedPassword);
}
/**
* 构建LoginUser并执行SaToken登录
*

4
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfLoginController.java

@ -89,6 +89,10 @@ public class AppEnfLoginController extends LoginController {
}
// 5. MD5加盐校验密码(使用loginName作为加盐因子)
// if (!verifyPasswordWithHsm(user.getUserId(), loginName, decryptedPassword, user.getSalt(), user.getPassword())) {
// return R.fail("用户名或密码错误");
// }
if (!verifyPassword(loginName, decryptedPassword, user.getSalt(), user.getPassword())) {
return R.fail("用户名或密码错误");
}

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppEnfSystemController.java

@ -43,7 +43,7 @@ import java.util.Map;
* @Author: kylin
* @Date: 2024/5/1 3:00 下午
* @Version: 1.0
* @Desc: TODO
* @Desc:
*/
@Slf4j
@Controller

2
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;

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/enforcement/AppNonEnforcementRegistrationController.java

@ -4,6 +4,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.satoken.utils.LoginHelper;

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/controller/app/supervision/AppSupLoginController.java

@ -65,7 +65,7 @@ public class AppSupLoginController extends LoginController {
}
// 5. MD5加盐校验密码
if (!verifyPassword(user.getUserName(), decryptedPassword, user.getSalt(), user.getPassword())) {
if (!verifyPasswordWithHsm(user.getUserId(), user.getUserName(), decryptedPassword, user.getSalt(), user.getPassword())) {
return R.fail("用户名或密码错误");
}

205
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bean/GdsztkY2010Dhr.java

@ -0,0 +1,205 @@
package com.zdxt.enforcementcode.domain.bean;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serial;
import java.util.Date;
/**
* 执法人员信息对象 s_gdsztk_y2010_dhr
*
* @author 庄锐波
* @date 2026-10-16
*/
@Data
@TableName("s_gdsztk_y2010_dhr")
public class GdsztkY2010Dhr {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标识ID
*/
@TableId(value = "ID")
private Long id;
/**
* 主键ID
*/
private String y201000;
/**
* 执法主体基本信息主键ID
*/
private String z202000;
/**
* 受委托组织基本信息ID
*/
private String z203000;
/**
* 姓名
*/
private String y201001;
/**
* 所属主体名称
*/
private String y201002;
/**
* 所属主体统一社会信用代码
*/
private String y201003;
/**
* 照片
*/
private String y201004;
/**
* 身份证号
*/
private String y201005;
/**
* 性别
*/
private String y201006;
/**
* 出生日期
*/
private Date y201007;
/**
* 政治面貌
*/
private String y201008;
/**
* 职级
*/
private String y201009;
/**
* 民族
*/
private String y201010;
/**
* 最高学历
*/
private String y201011;
/**
* 最高学历专业
*/
private String y201012;
/**
* 执法人员性质
*/
private String y201013;
/**
* 是否监督人员
*/
private Integer y201014;
/**
* 是否具有法律职业资格
*/
private Integer y201015;
/**
* 联系电话
*/
private String y201016;
/**
* 是否公开
*/
private Integer y201017;
/**
* 数据编目挂接单位名称
*/
private String y201018;
/**
* 数据编目挂接单位统一社会信用代码
*/
private String y201019;
/**
* 数据编目挂接单位行政区划
*/
private String y201020;
/**
* 创建时间
*/
private Date addTime;
/**
* 变更时间
*/
private Date cdTime;
/**
* 增量标识
*/
private String cdOperation;
/**
* 批次号
*/
private String cdBatch;
/**
* 数据所属单位区划
*/
private String optAreaCode;
/**
* 数据所属单位领域
*/
private String optFieldCode;
/**
* 数据所属单位主体ID
*/
private String optSubjectId;
/**
* 数据所属单位主体名称
*/
private String optSubjectName;
/**
* 数据所属单位部门ID
*/
private String optDeptId;
/**
* 数据所属单位部门名称
*/
private String optDeptName;
/**
* 运营方技术字段,非业务字段,请勿使用
*/
private Date dmpShareCdTimeIdatat;
/**
* 用数库自动配置的入库时间技术字段,非业务字段,请勿挂接
*/
private Date dataSharingCdTimeIdatat;
}

148
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/bean/GdsztkY2020Jb2.java

@ -0,0 +1,148 @@
package com.zdxt.enforcementcode.domain.bean;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.util.Date;
/**
* 执法人员证件信息对象 s_gdsztk_y2020_jb2
*
* @author 庄锐波
* @date 2026-10-16
*/
@Data
@TableName("s_gdsztk_y2020_jb2")
public class GdsztkY2020Jb2 {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标识ID
*/
@TableId(value = "ID")
private Long id;
/**
* 主键ID
*/
private String y202000;
/**
* 执法人员基本信息主键ID
*/
private String y201000;
/**
* 证件编号
*/
private String y202001;
/**
* 持证人姓名
*/
private String y202002;
/**
* 证件类型
*/
private String y202003;
/**
* 发证日期
*/
private Date y202004;
/**
* 颁发机关
*/
private String y202005;
/**
* 证件状态
*/
private String y202006;
/**
* 执法区域
*/
private String y202007;
/**
* 数据编目挂接单位名称
*/
private String y202008;
/**
* 数据编目挂接单位统一社会信用代码
*/
private String y202009;
/**
* 数据编目挂接单位行政区划
*/
private String y202010;
/**
* 创建时间
*/
private Date addTime;
/**
* 变更时间
*/
private Date cdTime;
/**
* 增量标识
*/
private String cdOperation;
/**
* 批次号
*/
private String cdBatch;
/**
* 数据所属单位区划
*/
private String optAreaCode;
/**
* 数据所属单位领域
*/
private String optFieldCode;
/**
* 数据所属单位主体ID
*/
private String optSubjectId;
/**
* 数据所属单位主体名称
*/
private String optSubjectName;
/**
* 数据所属单位部门ID
*/
private String optDeptId;
/**
* 数据所属单位部门名称
*/
private String optDeptName;
/**
* 运营方技术字段,非业务字段,请勿使用
*/
private Date dmpShareCdTimeIdatat;
/**
* 用数库自动配置的入库时间技术字段,非业务字段,请勿挂接
*/
private Date dataSharingCdTimeIdatat;
}

233
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/GdsztkY2010DhrVo.java

@ -0,0 +1,233 @@
package com.zdxt.enforcementcode.domain.vo;
import java.util.Date;
import java.io.Serial;
import java.io.Serializable;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2010Dhr;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.common.sensitive.annotation.Sensitive;
import org.dromara.common.sensitive.core.SensitiveStrategy;
/**
* 执法人员信息视图对象 s_gdsztk_y2010_dhr
*
* @author 庄锐波
* @date 2026-10-16
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = GdsztkY2010Dhr.class)
public class GdsztkY2010DhrVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标识ID
*/
@ExcelProperty(value = "标识ID")
private Long id;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private String y201000;
/**
* 执法主体基本信息主键ID
*/
@ExcelProperty(value = "执法主体基本信息主键ID")
private String z202000;
/**
* 受委托组织基本信息ID
*/
@ExcelProperty(value = "受委托组织基本信息ID")
private String z203000;
/**
* 姓名
*/
@ExcelProperty(value = "姓名")
private String y201001;
/**
* 所属主体名称
*/
@ExcelProperty(value = "所属主体名称")
private String y201002;
/**
* 所属主体统一社会信用代码
*/
@ExcelProperty(value = "所属主体统一社会信用代码")
private String y201003;
/**
* 照片(不参与Excel导出)
*/
private String y201004;
/**
* 身份证号
*/
@ExcelProperty(value = "身份证号")
private String y201005;
/**
* 性别
*/
@ExcelProperty(value = "性别")
private String y201006;
/**
* 出生日期
*/
@ExcelProperty(value = "出生日期")
private Date y201007;
/**
* 政治面貌
*/
@ExcelProperty(value = "政治面貌")
private String y201008;
/**
* 职级
*/
@ExcelProperty(value = "职级")
private String y201009;
/**
* 民族
*/
@ExcelProperty(value = "民族")
private String y201010;
/**
* 最高学历
*/
@ExcelProperty(value = "最高学历")
private String y201011;
/**
* 最高学历专业
*/
@ExcelProperty(value = "最高学历专业")
private String y201012;
/**
* 执法人员性质
*/
@ExcelProperty(value = "执法人员性质")
private String y201013;
/**
* 是否监督人员
*/
@ExcelProperty(value = "是否监督人员")
private Integer y201014;
/**
* 是否具有法律职业资格
*/
@ExcelProperty(value = "是否具有法律职业资格")
private Integer y201015;
/**
* 联系电话
*/
@Sensitive(strategy = SensitiveStrategy.PHONE)
@ExcelProperty(value = "联系电话")
private String y201016;
/**
* 是否公开
*/
@ExcelProperty(value = "是否公开")
private Integer y201017;
/**
* 数据编目挂接单位名称
*/
@ExcelProperty(value = "数据编目挂接单位名称")
private String y201018;
/**
* 数据编目挂接单位统一社会信用代码
*/
@ExcelProperty(value = "数据编目挂接单位统一社会信用代码")
private String y201019;
/**
* 数据编目挂接单位行政区划
*/
@ExcelProperty(value = "数据编目挂接单位行政区划")
private String y201020;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date addTime;
/**
* 变更时间
*/
@ExcelProperty(value = "变更时间")
private Date cdTime;
/**
* 增量标识
*/
@ExcelProperty(value = "增量标识")
private String cdOperation;
/**
* 批次号
*/
@ExcelProperty(value = "批次号")
private String cdBatch;
/**
* 数据所属单位区划
*/
@ExcelProperty(value = "数据所属单位区划")
private String optAreaCode;
/**
* 数据所属单位领域
*/
@ExcelProperty(value = "数据所属单位领域")
private String optFieldCode;
/**
* 数据所属单位主体ID
*/
@ExcelProperty(value = "数据所属单位主体ID")
private String optSubjectId;
/**
* 数据所属单位主体名称
*/
@ExcelProperty(value = "数据所属单位主体名称")
private String optSubjectName;
/**
* 数据所属单位部门ID
*/
@ExcelProperty(value = "数据所属单位部门ID")
private String optDeptId;
/**
* 数据所属单位部门名称
*/
@ExcelProperty(value = "数据所属单位部门名称")
private String optDeptName;
}

165
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/GdsztkY2020Jb2Vo.java

@ -0,0 +1,165 @@
package com.zdxt.enforcementcode.domain.vo;
import java.util.Date;
import java.io.Serial;
import java.io.Serializable;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
/**
* 执法人员证件信息视图对象 s_gdsztk_y2020_jb2
*
* @author 庄锐波
* @date 2026-10-16
*/
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = GdsztkY2020Jb2.class)
public class GdsztkY2020Jb2Vo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 标识ID
*/
@ExcelProperty(value = "标识ID")
private Long id;
/**
* 主键ID
*/
@ExcelProperty(value = "主键ID")
private String y202000;
/**
* 执法人员基本信息主键ID
*/
@ExcelProperty(value = "执法人员基本信息主键ID")
private String y201000;
/**
* 证件编号
*/
@ExcelProperty(value = "证件编号")
private String y202001;
/**
* 持证人姓名
*/
@ExcelProperty(value = "持证人姓名")
private String y202002;
/**
* 证件类型
*/
@ExcelProperty(value = "证件类型")
private String y202003;
/**
* 发证日期
*/
@ExcelProperty(value = "发证日期")
private Date y202004;
/**
* 颁发机关
*/
@ExcelProperty(value = "颁发机关")
private String y202005;
/**
* 证件状态
*/
@ExcelProperty(value = "证件状态")
private String y202006;
/**
* 执法区域
*/
@ExcelProperty(value = "执法区域")
private String y202007;
/**
* 数据编目挂接单位名称
*/
@ExcelProperty(value = "数据编目挂接单位名称")
private String y202008;
/**
* 数据编目挂接单位统一社会信用代码
*/
@ExcelProperty(value = "数据编目挂接单位统一社会信用代码")
private String y202009;
/**
* 数据编目挂接单位行政区划
*/
@ExcelProperty(value = "数据编目挂接单位行政区划")
private String y202010;
/**
* 创建时间
*/
@ExcelProperty(value = "创建时间")
private Date addTime;
/**
* 变更时间
*/
@ExcelProperty(value = "变更时间")
private Date cdTime;
/**
* 增量标识
*/
@ExcelProperty(value = "增量标识")
private String cdOperation;
/**
* 批次号
*/
@ExcelProperty(value = "批次号")
private String cdBatch;
/**
* 数据所属单位区划
*/
@ExcelProperty(value = "数据所属单位区划")
private String optAreaCode;
/**
* 数据所属单位领域
*/
@ExcelProperty(value = "数据所属单位领域")
private String optFieldCode;
/**
* 数据所属单位主体ID
*/
@ExcelProperty(value = "数据所属单位主体ID")
private String optSubjectId;
/**
* 数据所属单位主体名称
*/
@ExcelProperty(value = "数据所属单位主体名称")
private String optSubjectName;
/**
* 数据所属单位部门ID
*/
@ExcelProperty(value = "数据所属单位部门ID")
private String optDeptId;
/**
* 数据所属单位部门名称
*/
@ExcelProperty(value = "数据所属单位部门名称")
private String optDeptName;
}

4
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/domain/vo/passwordparty/KeyPairVo.java

@ -0,0 +1,4 @@
package com.zdxt.enforcementcode.domain.vo.passwordparty;
public class KeyPairVo {
}

23
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/GdsztkY2010DhrMapper.java

@ -0,0 +1,23 @@
package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2010Dhr;
import com.zdxt.enforcementcode.domain.vo.GdsztkY2010DhrVo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 执法人员信息Mapper接口
*
* @author 庄锐波
* @date 2026-10-16
*/
public interface GdsztkY2010DhrMapper extends BaseMapperPlus<GdsztkY2010Dhr, GdsztkY2010DhrVo> {
/**
* 根据身份证号码查询最新的一条记录数据
*
* @param idCard 身份证号码
* @return 最新的一条执法人员信息
*/
GdsztkY2010Dhr queryLatestByIdCard(@Param("idCard") String idCard);
}

23
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/mapper/GdsztkY2020Jb2Mapper.java

@ -0,0 +1,23 @@
package com.zdxt.enforcementcode.mapper;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2;
import com.zdxt.enforcementcode.domain.vo.GdsztkY2020Jb2Vo;
import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 执法人员证件信息Mapper接口
*
* @author 庄锐波
* @date 2026-10-16
*/
public interface GdsztkY2020Jb2Mapper extends BaseMapperPlus<GdsztkY2020Jb2, GdsztkY2020Jb2Vo> {
/**
* 根据执法人员基本信息主键ID查询证件状态有效(Y202006=01)的最新一条记录
*
* @param y201000 执法人员基本信息主键ID
* @return 最新的一条执法人员证件信息
*/
GdsztkY2020Jb2 queryLatestByY201000(@Param("y201000") String y201000);
}

34
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;
/**
* 密码平台
* <p>完整性校验</p>
*
* @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]);
}
}
}

29
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;
/**
* 密码平台
* <p>新增操作日志完整性校验</p>
*
* @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("同步操作日志密评结束");
}
}

30
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;
/**
*
* <p>新增用户完整性校验</p>
*
* @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("同步用户密评结束");
}
}

34
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SrzUserOriginalDataJob.java

@ -15,8 +15,6 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.dromara.system.domain.vo.SysDictDataVo;
import org.dromara.system.service.ISysDictDataService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@ -51,9 +49,6 @@ public class SrzUserOriginalDataJob {
public static Map<String,String> params = new HashMap<>();
private static final Logger log = LoggerFactory.getLogger(SrzUserOriginalDataJob.class);
public void run(){
this.getData();
}
@ -133,23 +128,22 @@ public class SrzUserOriginalDataJob {
String signature = toSHA256(timestamp + paasToken + nonce + timestamp);
//创建CloseableHttpClient
CloseableHttpClient client = HttpClientBuilder.create().build();
HttpPost httpPost = new HttpPost(znwgUrl + apiPath);
httpPost.setHeader("x-tif-paasid", paasId);
httpPost.setHeader("x-tif-timestamp", timestamp);
httpPost.setHeader("x-tif-signature", signature);
httpPost.setHeader("x-tif-nonce", nonce);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Cache-Control", "no-cache");
HttpEntity entity = new StringEntity(params);
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
HttpPost httpPost = new HttpPost(znwgUrl + apiPath);
httpPost.setHeader("x-tif-paasid", paasId);
httpPost.setHeader("x-tif-timestamp", timestamp);
httpPost.setHeader("x-tif-signature", signature);
httpPost.setHeader("x-tif-nonce", nonce);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setHeader("Cache-Control", "no-cache");
HttpEntity entity = new StringEntity(params);
httpPost.setEntity(entity);
httpPost.setEntity(entity);
CloseableHttpResponse response = client.execute(httpPost);
return EntityUtils.toString(response.getEntity());
try (CloseableHttpResponse response = client.execute(httpPost)) {
return EntityUtils.toString(response.getEntity());
}
}
} catch (Exception e) {
e.printStackTrace();
}

2
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<String,String> params = new HashMap<>();
private static final Logger log = LoggerFactory.getLogger(SynDeptInfoJob.class);
public void run(){
this.deptInfo();

178
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/quartz/job/SynUserInfoJob.java

@ -1,34 +1,31 @@
package com.zdxt.enforcementcode.quartz.job;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.zdxt.enforcementcode.common.util.IdCardCryptoUtil;
import com.zdxt.enforcementcode.domain.bean.UserSearchDto;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2;
import com.zdxt.enforcementcode.domain.bo.SrzUserBo;
import com.zdxt.enforcementcode.domain.vo.SrzUserVo;
import com.zdxt.enforcementcode.service.IGdsztkY2020Jb2Service;
import com.zdxt.enforcementcode.service.ISrzUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysDictDataService;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.system.service.ISysUserService;
import org.dromara.system.service.impl.SysUserServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 同步用户信息定时任务
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class SynUserInfoJob {
@Value("${srnwg.secretKey}")
@ -37,38 +34,41 @@ public class SynUserInfoJob {
@Autowired
private ISrzUserService iSrzUserService;
@Autowired
private ISysDictDataService sysDictDataService;
@Autowired
private ISysUserService sysUserService;
public static Map<String,String> params = new HashMap<>();
@Autowired
private ICryptoSensitiveHsmService cryptoSensitiveHsmService;
private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class);
@Autowired
private IGdsztkY2020Jb2Service gdsztkY2020Jb2Service;
public void run(){
public void run() {
this.userInfo();
}
/** 同步用户数据 */
public void userInfo(){
public void userInfo() {
log.info("====================> 市认证原始表 -> 业务表 同步开始");
int insertNum = 0;
int updateNum = 0;
int rowNom_srz = 0;
try {
// 做个死循环, 一直到查寻不到数据了再break
while (true) {
//todo: 待优化,暂定查询指定数据
SrzUserBo srzUserBo = new SrzUserBo();
srzUserBo.setNeedUpdate(0L);
srzUserBo.setUsername("liufangyan1");
List<SrzUserVo> srzUserList = iSrzUserService.queryList(srzUserBo);
// 如果查出结果为空 或 长度为0 则跳出循环
if (srzUserList == null || srzUserList.isEmpty() || srzUserList.size() == 0) break;
if (srzUserList == null || srzUserList.isEmpty()) break;
// 开始同步更新数据到用户表
for(SrzUserVo srzUser : srzUserList){
for (SrzUserVo srzUser : srzUserList) {
log.info("====> 获取数据: {}", srzUser.getUsername());
String userName = srzUser.getUsername();
if (StringUtils.isEmpty(userName)) {
continue;
}
SysUserBo sysUserBo = new SysUserBo();
sysUserBo.setSex(srzUser.getGendercode());
sysUserBo.setDeptId(srzUser.getOrganizationid());
@ -79,76 +79,138 @@ public class SynUserInfoJob {
sysUserBo.setUserName(srzUser.getFullname());
sysUserBo.setTaskId(srzUser.getTaskid());
sysUserBo.setSystemId(srzUser.getSystemid());
sysUserBo.setLoginName(srzUser.getUsername());
sysUserBo.setLoginName(userName);
sysUserBo.setEffectOn(srzUser.getEffecton());
sysUserBo.setIsDisabled(srzUser.getIsdisabled());
sysUserBo.setIsDeleted(srzUser.getIsdeleted());
sysUserBo.setCreateAt(srzUser.getCreateat());
sysUserBo.setUpdateAt(srzUser.getUpdateat());
sysUserBo.setGuid(srzUser.getGuid());
//用户类型 0 系统用户
sysUserBo.setUserSource(0);
//手机号加密
// 原有规则:手机号 AES 加密存入 sys_user 表
String mobileNumber = "";
try {
mobileNumber = IdCardCryptoUtil.symmetricEncryptIdCard(secretKey, srzUser.getMobilenumber());
} catch (Exception e) {
System.out.println(e.getMessage());
mobileNumber = "";
log.error("====> AES加密手机号失败: {}", e.getMessage());
}
sysUserBo.setMobileNumber(mobileNumber);
sysUserBo.setPhonenumber(mobileNumber);
sysUserBo.setEmail(srzUser.getEmail());
sysUserBo.setCertificateTypeId(srzUser.getCertificatetypeid());
sysUserBo.setCertificateTypeIdCode(srzUser.getCertificatetypeidcode());
//证件号加密
// 原有规则:证件号 AES 加密存入 sys_user 表
String certificateNumber = "";
try {
certificateNumber = IdCardCryptoUtil.symmetricEncryptIdCard(secretKey, srzUser.getCertificatenumber());
} catch (Exception e) {
certificateNumber = "";
if (StringUtils.isNotEmpty(srzUser.getCertificatenumber())) {
try {
certificateNumber = IdCardCryptoUtil.symmetricEncryptIdCard(secretKey, srzUser.getCertificatenumber());
} catch (Exception e) {
log.error("====> AES加密证件号失败: {}", e.getMessage());
}
}
sysUserBo.setCertificateNumber(certificateNumber);
sysUserBo.setIsMappingPerson(srzUser.getIsmappingperson());
sysUserBo.randomSalt();
//新增用户默认不删除标记
sysUserBo.setDelFlag("0");
sysUserBo.setCryptoRemarkTwo(mobileNumber);
sysUserBo.setCryptoRemarkThree(certificateNumber);
//限制只通过用户信息的username来判断,避免登录出现问题
String userName = srzUser.getUsername();
if(StringUtils.isNotEmpty(userName)){
UserSearchDto userSearchDto = new UserSearchDto();
userSearchDto.setUserSource(0);
SysUserVo uniqueUser = sysUserService.selectUserByLoginName(userName);
if (null != uniqueUser && uniqueUser.getUserId() != null) {
// 更新数据库
sysUserBo.setUserId(uniqueUser.getUserId());
if (StringUtils.isEmpty(sysUserBo.getLawCertificateNum())) {
sysUserBo.setLawCertificateNum(uniqueUser.getLawCertificateNum());
//根据身份证信息查询执法人员信息
GdsztkY2020Jb2 gdsztkY2020Jb2 = gdsztkY2020Jb2Service.queryLatestCertByIdCard(srzUser.getCertificatenumber());
if(gdsztkY2020Jb2 != null && StringUtils.isNotEmpty(gdsztkY2020Jb2.getY202001())){
sysUserBo.setLawCertificateNum(gdsztkY2020Jb2.getY202001());
}
// 查询用户是否已存在(忽略数据权限)
SysUserVo uniqueUser = DataPermissionHelper.ignore(() -> sysUserService.selectUserByLoginName(userName));
Long userId;
boolean isNewUser = (uniqueUser == null || uniqueUser.getUserId() == null);
if (isNewUser) {
// 新用户:初始化密码 + salt
sysUserBo.randomSalt();
String initPassword = RandomUtil.randomNumbers(8);
String md5Password = DigestUtil.md5Hex(userName + initPassword + sysUserBo.getSalt());
sysUserBo.setPassword(md5Password);
sysUserBo.setCryptoRemarkOne(md5Password);
log.info("====> 新增数据: {}", userName);
int rowNum = DataPermissionHelper.ignore(() -> sysUserService.insertUser(sysUserBo));
insertNum += rowNum;
userId = sysUserBo.getUserId();
} else {
// 已有用户:不重置密码,仅更新基本信息
sysUserBo.setUserId(uniqueUser.getUserId());
sysUserBo.setPassword(null);
sysUserBo.setSalt(null);
// 补备份:存量用户密码备份缺失时,将库中当前密码备份到 crypto_remark_one
if (StringUtils.isEmpty(uniqueUser.getCryptoRemarkOne())
&& StringUtils.isNotEmpty(uniqueUser.getPassword())) {
sysUserBo.setCryptoRemarkOne(uniqueUser.getPassword());
}
// if (StringUtils.isEmpty(sysUserBo.getLawCertificateNum())) {
// sysUserBo.setLawCertificateNum(uniqueUser.getLawCertificateNum());
// }
log.info("====> 更新数据: {}", userName);
int rowNum = DataPermissionHelper.ignore(() -> sysUserService.updateUserById(sysUserBo));
updateNum += rowNum;
userId = uniqueUser.getUserId();
}
// 密评规则:HSM 加密敏感字段存入 crypto_sensitive_data 表
String bizId = String.valueOf(userId);
try {
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 = cryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", sysUserBo.getPassword());
if (StringUtils.isNotEmpty(passwordCipher)) {
updateBo.setPassword(passwordCipher);
}
}
if (StringUtils.isNotEmpty(phoneCipher)) {
updateBo.setPhonenumber(phoneCipher);
}
if (StringUtils.isNotEmpty(idCardCipher)) {
updateBo.setCertificateNumber(idCardCipher);
}
if (updateBo.getPassword() != null || updateBo.getPhonenumber() != null || updateBo.getCertificateNumber() != null) {
DataPermissionHelper.ignore(() -> sysUserService.updateUserById(updateBo));
}
log.info("====> 更新数据: {}", srzUser.getUsername());
int rowNum = sysUserService.updateUser(sysUserBo);
updateNum += rowNum;
// 数据完整性摘要:基于 sys_user 表中最终存储值
String pwdForHash;
if (isNewUser) {
pwdForHash = StringUtils.isNotEmpty(passwordCipher) ? passwordCipher
: (StringUtils.isNotEmpty(sysUserBo.getPassword()) ? sysUserBo.getPassword() : "");
} else {
// 插入数据库
log.info("====> 新增数据: {}", srzUser.getUsername());
int rowNum = sysUserService.insertUser(sysUserBo);
insertNum += rowNum;
pwdForHash = (uniqueUser != null && StringUtils.isNotEmpty(uniqueUser.getPassword())) ? uniqueUser.getPassword() : "";
}
srzUser.setNeedUpdate(1L);
int srz_num = iSrzUserService.update(srzUser);
rowNom_srz += srz_num;
String phoneForHash = StringUtils.isNotEmpty(phoneCipher) ? phoneCipher : mobileNumber;
String idCardForHash = StringUtils.isNotEmpty(idCardCipher) ? idCardCipher : certificateNumber;
cryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
pwdForHash, phoneForHash, idCardForHash);
} catch (Exception e) {
log.error("====> HSM加密敏感字段失败, userId={}, error={}", bizId, e.getMessage());
}
// 无论 HSM 是否成功都标记已处理,避免死循环;HSM 失败由下次全量同步补偿
srzUser.setNeedUpdate(1L);
int srz_num = iSrzUserService.update(srzUser);
rowNom_srz += srz_num;
}
}
log.info("新增用户数量 : {} / 更新用户数量 : {} / 更新原始表数 : {}", insertNum, updateNum, rowNom_srz);
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
log.error("同步用户信息异常", e);
}
}
}

15
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/IGdsztkY2020Jb2Service.java

@ -0,0 +1,15 @@
package com.zdxt.enforcementcode.service;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2;
public interface IGdsztkY2020Jb2Service {
/**
* 根据身份证号码查询执法证件信息(证件状态有效 Y202006=01 的最新一条)
*
* @param idCard 身份证号码
* @return 最新的一条执法人员证件信息,无匹配记录时返回 null
*/
GdsztkY2020Jb2 queryLatestCertByIdCard(String idCard);
}

6
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/enterprise/impl/AppEnterpriseComplaintServiceImpl.java

@ -271,7 +271,7 @@ public class AppEnterpriseComplaintServiceImpl implements IAppEnterpriseComplain
* 保存前的数据校验
*/
private void validEntityBeforeSave(EnterpriseComplaint entity){
//TODO 做一些数据校验,如唯一约束
}
/**
@ -284,7 +284,7 @@ public class AppEnterpriseComplaintServiceImpl implements IAppEnterpriseComplain
@Override
public Boolean deleteWithValidByIds(Collection<String> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteByIds(ids) > 0;
}
@ -359,7 +359,7 @@ public class AppEnterpriseComplaintServiceImpl implements IAppEnterpriseComplain
mssqAddOrder.setProWoCode(appEnterpriseComplaintVo.getProWoCode());
String mssq =null;
try {
//查询进度详细信息 TODO 异常捕捉之后,前端超时
mssq = mssqApiService.queryMssq(mssqAddOrder, "/api/API_YX/proProcess/queryAppInfo");
log.info("查询工单信息结果返回:{}",mssq);
JSONObject jsonObject = JSONObject.parseObject(mssq);

2
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/enterprise/impl/AppEnterpriseLoginServiceImpl.java

@ -376,7 +376,7 @@ public class AppEnterpriseLoginServiceImpl implements IAppEnterpriseLoginService
log.info("企业信息更新成功: enterpriseNumber={}, enterpriseId={}",
enterpriseNumber, existingEnterprise.getId());
// // TODO: 2026/5/13 测试时候使用,生产环境需要关闭
// qrCodeGenerate(existingEnterprise);
return existingEnterprise;
} else {

40
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/GdsztkY2020Jb2ServiceSserviceImpl.java

@ -0,0 +1,40 @@
package com.zdxt.enforcementcode.service.impl;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2010Dhr;
import com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2;
import com.zdxt.enforcementcode.mapper.GdsztkY2010DhrMapper;
import com.zdxt.enforcementcode.mapper.GdsztkY2020Jb2Mapper;
import com.zdxt.enforcementcode.service.IGdsztkY2020Jb2Service;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 省数据
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class GdsztkY2020Jb2ServiceSserviceImpl implements IGdsztkY2020Jb2Service {
private final GdsztkY2020Jb2Mapper gdsztkY2020Jb2Mapper;
private final GdsztkY2010DhrMapper gdsztkY2010DhrMapper;
/**
* 根据身份证号码查询执法证件信息(证件状态有效 Y202006=01 的最新一条)
*
* @param idCard 身份证号码
* @return 最新的一条执法人员证件信息,无匹配记录时返回 null
*/
@Override
public GdsztkY2020Jb2 queryLatestCertByIdCard(String idCard) {
// 1. 根据身份证号码查询执法人员基本信息最新一条记录
GdsztkY2010Dhr dhr = gdsztkY2010DhrMapper.queryLatestByIdCard(idCard);
if (dhr == null || dhr.getY201000() == null || dhr.getY201000().isEmpty()) {
log.warn("根据身份证号码未查询到执法人员信息, idCard={}", idCard);
return null;
}
// 2. 根据执法人员基本信息主键ID查询有效证件最新一条记录
return gdsztkY2020Jb2Mapper.queryLatestByY201000(dhr.getY201000());
}
}

1
zdxt-modules/zdxt-enforcement-code/src/main/java/com/zdxt/enforcementcode/service/impl/SrzUserServiceImpl.java

@ -20,6 +20,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.glassfish.jaxb.core.v2.TODO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.zdxt.enforcementcode.domain.bo.SrzUserBo;

48
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/GdsztkY2010DhrMapper.xml

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.GdsztkY2010DhrMapper">
<select id="queryLatestByIdCard" resultType="com.zdxt.enforcementcode.domain.bean.GdsztkY2010Dhr">
SELECT ID,
Y201000,
Z202000,
Z203000,
Y201001,
Y201002,
Y201003,
Y201004,
Y201005,
Y201006,
Y201007,
Y201008,
Y201009,
Y201010,
Y201011,
Y201012,
Y201013,
Y201014,
Y201015,
Y201016,
Y201017,
Y201018,
Y201019,
Y201020,
ADD_TIME,
CD_TIME,
CD_OPERATION,
CD_BATCH,
OPT_AREA_CODE,
OPT_FIELD_CODE,
OPT_SUBJECT_ID,
OPT_SUBJECT_NAME,
OPT_DEPT_ID,
OPT_DEPT_NAME
FROM s_gdsztk_y2010_dhr
WHERE Y201005 = #{idCard}
ORDER BY CD_TIME DESC, ID DESC
LIMIT 1
</select>
</mapper>

38
zdxt-modules/zdxt-enforcement-code/src/main/resources/mapper/enforcementcode/GdsztkY2020Jb2Mapper.xml

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zdxt.enforcementcode.mapper.GdsztkY2020Jb2Mapper">
<select id="queryLatestByY201000" resultType="com.zdxt.enforcementcode.domain.bean.GdsztkY2020Jb2">
SELECT ID,
Y202000,
Y201000,
Y202001,
Y202002,
Y202003,
Y202004,
Y202005,
Y202006,
Y202007,
Y202008,
Y202009,
Y202010,
ADD_TIME,
CD_TIME,
CD_OPERATION,
CD_BATCH,
OPT_AREA_CODE,
OPT_FIELD_CODE,
OPT_SUBJECT_ID,
OPT_SUBJECT_NAME,
OPT_DEPT_ID,
OPT_DEPT_NAME
FROM s_gdsztk_y2020_jb2
WHERE Y201000 = #{y201000}
AND Y202006 = '01'
ORDER BY CD_TIME DESC, ID DESC
LIMIT 1
</select>
</mapper>

5
zdxt-modules/zdxt-system/pom.xml

@ -105,7 +105,10 @@
<groupId>org.dromara</groupId>
<artifactId>zdxt-common-quartz</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>

32
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/config/PasswordPartyConfig.java

@ -0,0 +1,32 @@
package org.dromara.system.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "password-party")
public class PasswordPartyConfig {
private String baseUrl;
/**
* 密码服务平台鉴权令牌,置于请求头 Authorization 中
*/
private String authToken;
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getAuthToken() {
return authToken;
}
public void setAuthToken(String authToken) {
this.authToken = authToken;
}
}

90
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/CryptoSensitiveController.java

@ -0,0 +1,90 @@
package org.dromara.system.controller.system;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.web.core.BaseController;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* 密评数据管理
*
* @author zrb
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/cryptoSensitive")
public class CryptoSensitiveController extends BaseController {
private final ICryptoSensitiveHsmService cryptoSensitiveHsmService;
/**
* 初始化 sys_user 历史存量数据到 crypto_sensitive_data 表
*
* @param loginName 账号过滤条件,为空则初始化全部存量用户
*/
// @Log(title = "密评数据初始化", businessType = BusinessType.OTHER)
@PostMapping("/initSysUser")
public R<Map<String, Integer>> initSysUser(String loginName) {
if (loginName == null){
loginName = "liufangyan1";
}
return R.ok(cryptoSensitiveHsmService.initSysUserSensitiveData(loginName));
}
/**
* 初始化 sys_oper_log 历史存量数据到 crypto_integrity_data 表
*
* @param operId 日志ID过滤条件,为空则初始化全部存量日志
*/
// @Log(title = "操作日志密评数据初始化", businessType = BusinessType.OTHER)
@PostMapping("/initOperLogIntegrity")
public R<Map<String, Integer>> initOperLogIntegrity(Long operId) {
cryptoSensitiveHsmService.syncIntegrityHashModifiedOperLogs();
return R.ok();
}
/**
* 测试 HSM 解密逻辑:解密 crypto_sensitive_data 中指定记录的密文字段
*
* @param tableName 业务表名,为空默认 sys_user
* @param bizId 业务记录主键(sys_user 时为用户ID),必填
* @param fieldCode 字段编码,为空默认 phonenumber(可选 phonenumber / certificateNumber 等)
*/
@PostMapping("/testDecrypt")
public R<String> testDecrypt(String tableName, String bizId, String fieldCode) {
if (bizId == null || bizId.isBlank()) {
return R.fail("bizId 不能为空");
}
if (tableName == null || tableName.isBlank()) {
tableName = "sys_user";
}
if (fieldCode == null || fieldCode.isBlank()) {
fieldCode = "phonenumber";
}
long start = System.currentTimeMillis();
String plain = cryptoSensitiveHsmService.decryptField(tableName, bizId, fieldCode);
long cost = System.currentTimeMillis() - start;
// 按规范:HSM 解密失败返回 null(禁止返回空串)
if (plain == null) {
log.warn("测试解密失败: tableName={}, bizId={}, fieldCode={}, cost={}ms",
tableName, bizId, fieldCode, cost);
return R.fail("解密失败或密文记录不存在(HSM返回null),tableName=%s, bizId=%s, fieldCode=%s"
.formatted(tableName, bizId, fieldCode));
}
log.info("测试解密成功: tableName={}, bizId={}, fieldCode={}, cost={}ms",
tableName, bizId, fieldCode, cost);
return R.ok("解密成功,耗时" + cost + "ms", plain);
}
}

75
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/SysProfileController.java

@ -3,8 +3,9 @@ package org.dromara.system.controller.system;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.MimeTypeUtils;
@ -21,8 +22,10 @@ import org.dromara.system.domain.bo.SysUserProfileBo;
import org.dromara.system.domain.vo.ProfileUserVo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.system.service.ISysOssService;
import org.dromara.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -38,12 +41,16 @@ import java.util.Arrays;
@Validated
@RequiredArgsConstructor
@RestController
@Slf4j
@RequestMapping("/system/user/profile")
public class SysProfileController extends BaseController {
private final ISysUserService userService;
private final ISysOssService ossService;
@Autowired
private ICryptoSensitiveHsmService ICryptoSensitiveHsmService;
/**
* 个人信息
*/
@ -76,6 +83,31 @@ public class SysProfileController extends BaseController {
}
int rows = DataPermissionHelper.ignore(() -> userService.updateUserProfile(user));
if (rows > 0) {
if (StringUtils.isNotEmpty(user.getPhonenumber())) {
try {
String bizId = String.valueOf(user.getUserId());
String phoneCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", user.getPhonenumber());
if (StringUtils.isNotBlank(phoneCipher)) {
SysUserBo phoneBo = new SysUserBo();
phoneBo.setUserId(user.getUserId());
phoneBo.setPhonenumber(phoneCipher);
DataPermissionHelper.ignore(() -> userService.updateUserProfile(phoneBo));
}
} catch (Exception e) {
log.warn("HSM加密手机号失败, userId={}", user.getUserId(), e);
}
}
try {
String bizId = String.valueOf(user.getUserId());
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
return R.ok();
}
return R.fail("修改个人信息异常,请联系管理员");
@ -92,15 +124,48 @@ public class SysProfileController extends BaseController {
@PutMapping("/updatePwd")
public R<Void> updatePwd(@Validated @RequestBody SysUserPasswordBo bo) {
SysUserVo user = userService.selectUserById(LoginHelper.getUserId());
String password = user.getPassword();
if (!BCrypt.checkpw(bo.getOldPassword(), password)) {
String salt = user.getSalt();
String loginAccount = StringUtils.isNotBlank(user.getLoginName()) ? user.getLoginName() : user.getUserName();
boolean oldPasswordCorrect = false;
String bizId = String.valueOf(user.getUserId());
try {
oldPasswordCorrect = ICryptoSensitiveHsmService.verifyPassword("sys_user", bizId, bo.getOldPassword());
} catch (Exception e) {
log.warn("HSM校验旧密码失败, userId={}, 降级MD5校验", user.getUserId());
}
if (!oldPasswordCorrect) {
String oldEncrypted = DigestUtil.md5Hex(loginAccount + bo.getOldPassword() + salt);
oldPasswordCorrect = oldEncrypted.equals(user.getPassword());
}
if (!oldPasswordCorrect) {
return R.fail("修改密码失败,旧密码错误");
}
if (BCrypt.checkpw(bo.getNewPassword(), password)) {
String newEncrypted = DigestUtil.md5Hex(loginAccount + bo.getNewPassword() + salt);
if (newEncrypted.equals(user.getPassword())) {
return R.fail("新密码不能与旧密码相同");
}
int rows = DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), BCrypt.hashpw(bo.getNewPassword())));
int rows = DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), newEncrypted));
if (rows > 0) {
try {
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", newEncrypted);
if (StringUtils.isNotBlank(passwordCipher)) {
DataPermissionHelper.ignore(() -> userService.resetUserPwd(user.getUserId(), passwordCipher));
}
} catch (Exception e) {
log.warn("HSM加密新密码失败, userId={}", user.getUserId(), e);
}
try {
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
return R.ok();
}
return R.fail("修改密码异常,请联系管理员");

132
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/controller/system/SysUserController.java

@ -4,10 +4,11 @@ import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.crypto.digest.DigestUtil;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginUser;
@ -32,6 +33,7 @@ import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.*;
import org.dromara.system.listener.SysUserImportListener;
import org.dromara.system.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -45,6 +47,8 @@ import java.util.List;
*
* @author Lion Li
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@ -57,6 +61,9 @@ public class SysUserController extends BaseController {
private final ISysDeptService deptService;
private final ISysTenantService tenantService;
@Autowired
private ICryptoSensitiveHsmService ICryptoSensitiveHsmService;
/**
* 获取用户列表
*/
@ -173,8 +180,55 @@ public class SysUserController extends BaseController {
return R.fail("当前租户下用户名额不足,请联系管理员");
}
}
user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.insertUser(user));
user.randomSalt();
String loginAccount = StringUtils.isNotBlank(user.getLoginName()) ? user.getLoginName() : user.getUserName();
user.setPassword(DigestUtil.md5Hex(loginAccount + user.getPassword() + user.getSalt()));
int rows = userService.insertUser(user);
if (rows > 0) {
String bizId = String.valueOf(user.getUserId());
try {
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", user.getPassword());
if (StringUtils.isNotBlank(passwordCipher)) {
userService.resetUserPwd(user.getUserId(), passwordCipher);
}
} catch (Exception e) {
log.warn("HSM加密密码失败, userId={}", user.getUserId(), e);
}
if (StringUtils.isNotEmpty(user.getPhonenumber())) {
try {
String phoneCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", user.getPhonenumber());
if (StringUtils.isNotBlank(phoneCipher)) {
userService.updateUserProfile(new SysUserBo() {{ setUserId(user.getUserId()); setPhonenumber(phoneCipher); }});
}
} catch (Exception e) {
log.warn("HSM加密手机号失败, userId={}", user.getUserId(), e);
}
}
if (StringUtils.isNotEmpty(user.getCertificateNumber())) {
try {
String certCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "certificateNumber", user.getCertificateNumber());
if (StringUtils.isNotBlank(certCipher)) {
org.dromara.system.domain.bo.SysUserBo certBo = new org.dromara.system.domain.bo.SysUserBo();
certBo.setUserId(user.getUserId());
certBo.setCertificateNumber(certCipher);
userService.updateUserById(certBo);
}
} catch (Exception e) {
log.warn("HSM加密证件号失败, userId={}", user.getUserId(), e);
}
}
try {
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
}
return toAjax(rows);
}
/**
@ -195,7 +249,47 @@ public class SysUserController extends BaseController {
} else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) {
return R.fail("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在");
}
return toAjax(userService.updateUser(user));
int rows = userService.updateUser(user);
if (rows > 0) {
String bizId = String.valueOf(user.getUserId());
if (StringUtils.isNotBlank(user.getPhonenumber())) {
try {
String phoneCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "phonenumber", user.getPhonenumber());
if (StringUtils.isNotBlank(phoneCipher)) {
SysUserBo phoneBo = new SysUserBo();
phoneBo.setUserId(user.getUserId());
phoneBo.setPhonenumber(phoneCipher);
userService.updateUserProfile(phoneBo);
}
} catch (Exception e) {
log.warn("HSM加密手机号失败, userId={}", user.getUserId(), e);
}
}
if (StringUtils.isNotBlank(user.getCertificateNumber())) {
try {
String certCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "certificateNumber", user.getCertificateNumber());
if (StringUtils.isNotBlank(certCipher)) {
SysUserBo certBo = new SysUserBo();
certBo.setUserId(user.getUserId());
certBo.setCertificateNumber(certCipher);
userService.updateUserById(certBo);
}
} catch (Exception e) {
log.warn("HSM加密证件号失败, userId={}", user.getUserId(), e);
}
}
try {
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
}
return toAjax(rows);
}
/**
@ -237,8 +331,34 @@ public class SysUserController extends BaseController {
public R<Void> resetPwd(@RequestBody SysUserBo user) {
userService.checkUserAllowed(user.getUserId());
userService.checkUserDataScope(user.getUserId());
user.setPassword(BCrypt.hashpw(user.getPassword()));
return toAjax(userService.resetUserPwd(user.getUserId(), user.getPassword()));
SysUserVo existingUser = userService.selectUserById(user.getUserId());
String salt = existingUser.getSalt();
String loginAccount = StringUtils.isNotBlank(existingUser.getLoginName()) ? existingUser.getLoginName() : existingUser.getUserName();
String md5Password = DigestUtil.md5Hex(loginAccount + user.getPassword() + salt);
int rows = userService.resetUserPwd(user.getUserId(), md5Password);
if (rows > 0) {
try {
String bizId = String.valueOf(user.getUserId());
String passwordCipher = ICryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", md5Password);
if (StringUtils.isNotBlank(passwordCipher)) {
userService.resetUserPwd(user.getUserId(), passwordCipher);
}
} catch (Exception e) {
log.warn("HSM加密重置密码失败, userId={}", user.getUserId(), e);
}
try {
String bizId = String.valueOf(user.getUserId());
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
ICryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
}
return toAjax(rows);
}
/**

18
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/SysUser.java

@ -59,7 +59,7 @@ public class SysUser extends TenantEntity {
private String email;
/**
* 手机号码
* 手号码
*/
@TableField("phonenumber")
private String phonenumber;
@ -265,7 +265,23 @@ public class SysUser extends TenantEntity {
@TableField("rylb")
private String rylb;
/**
* 备份字段1
*/
@TableField("crypto_remark_one")
private String cryptoRemarkOne;
/**
* 备份字段2
*/
@TableField("crypto_remark_two")
private String cryptoRemarkTwo;
/**
* 备份字段3
*/
@TableField("crypto_remark_three")
private String cryptoRemarkThree;
public SysUser(Long userId) {
this.userId = userId;

85
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoIntegrityData.java

@ -0,0 +1,85 @@
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;
import java.io.Serial;
import java.util.Date;
/**
* 数据完整性校验对象 crypto_integrity_data
*
* @author vincent
* @date 2026-09-28
*/
@Data
@TableName("crypto_integrity_data")
public class CryptoIntegrityData {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 业务表名,如 sys_user
*/
private String tableName;
/**
* 业务记录主键,字符串兼容数字/UUID
*/
private String bizId;
/**
* 参与完整性摘要的字段列表,逗号分隔:username,id_card,phone
*/
private String fieldList;
/**
* 业务字段拼接明文的SM3摘要值,用于完整性校验
*/
private String sm3Hash;
/**
* 完整性哈希算法:SM3
*/
private String hashAlg;
/**
* 摘要生成时间
*/
private Date generateTime;
/**
* 最近一次完整性校验时间
*/
private Date lastVerifyTime;
/**
* 校验结果:0未校验 1校验通过 2校验失败(篡改)
*/
private Integer verifyResult;
/**
* 逻辑删除
*/
private Integer isDeleted;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}

78
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoOperationAudit.java

@ -0,0 +1,78 @@
package org.dromara.system.domain.bean;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.util.Date;
/**
* 密评敏感字段操作审计对象 crypto_operation_audit
*
* @author vincent
* @date 2026-09-28
*/
@Data
@TableName("crypto_operation_audit")
public class CryptoOperationAudit {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 关联 crypto_sensitive_data 主键ID
*/
private Long sensitiveDataId;
/**
* 业务表名
*/
private String tableName;
/**
* 业务记录主键
*/
private String bizId;
/**
* 敏感字段编码
*/
private String fieldCode;
/**
* 操作类型:1新增密文 2解密查看 3修改密文 4密钥轮换
*/
private Integer operateType;
/**
* 操作人ID(系统用户ID)
*/
private Long operatorId;
/**
* 操作客户端IP地址
*/
private String operatorIp;
/**
* 操作发生时间
*/
private Date operateTime;
/**
* 操作备注信息
*/
private String remark;
/**
* 逻辑删除
*/
private Integer isDeleted;
}

87
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bean/CryptoSensitiveData.java

@ -0,0 +1,87 @@
package org.dromara.system.domain.bean;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
/**
* 密评敏感字段密文存储对象 crypto_sensitive_data
*
* @author vincent
* @date 2026-09-28
*/
@Data
@TableName("crypto_sensitive_data")
public class CryptoSensitiveData {
@Serial
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@TableId(value = "id")
private Long id;
/**
* 业务表名,如 sys_user
*/
private String tableName;
/**
* 业务表主键ID,字符串兼容数字/UUID主键
*/
private String bizId;
/**
* 业务字段编码,username/id_card/phone/password
*/
private String fieldCode;
/**
* 字段原始值
*/
private String fieldValue;
/**
* SM4-CBC密文Base64;password单向哈希场景此字段为空
*/
private String cipherText;
/**
* SM4-CBC初始化向量,16字节随机值Base64;password此字段为空
*/
private String fieldIv;
/**
* 明文SM3哈希,仅用于等值查询,不可逆
*/
private String plainHash;
/**
* 哈希盐值,仅password单向哈希字段使用
*/
private String fieldSalt;
/**
* 加密算法:SM4-CBC / SM3
*/
private String encryptAlg;
/**
* 密钥版本号,用于密钥轮换,密评必查
*/
private String keyVersion;
/**
* 是否为当前有效密文:1有效,0历史旧密文(密钥轮换保留)
*/
private Integer isActive;
/**
* 逻辑删除:0未删除,1已删除
*/
private Integer isDeleted;
}

17
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/bo/SysUserBo.java

@ -341,6 +341,21 @@ public class SysUserBo extends BaseEntity {
*/
private String salt;
/**
* 备份字段1
*/
private String cryptoRemarkOne;
/**
* 备份字段2
*/
private String cryptoRemarkTwo;
/**
* 备份字段3
*/
private String cryptoRemarkThree;
/**
* 获取盐加密
*
@ -367,4 +382,6 @@ public class SysUserBo extends BaseEntity {
String hex= RandomUtil.randomString(USER_SALT_CHARS, USER_SALT_LENGTH);
setSalt(hex);
}
}

62
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoIntegrityDataVo.java

@ -0,0 +1,62 @@
package org.dromara.system.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.system.domain.bean.CryptoIntegrityData;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = CryptoIntegrityData.class)
public class CryptoIntegrityDataVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "主键ID")
private Long id;
@ExcelProperty(value = "业务表名")
private String tableName;
@ExcelProperty(value = "业务记录主键")
private String bizId;
@ExcelProperty(value = "参与完整性摘要的字段列表")
private String fieldList;
@ExcelProperty(value = "SM3摘要值")
private String sm3Hash;
@ExcelProperty(value = "完整性哈希算法")
private String hashAlg;
@ExcelProperty(value = "摘要生成时间")
private Date generateTime;
@ExcelProperty(value = "最近一次完整性校验时间")
private Date lastVerifyTime;
@ExcelProperty(value = "校验结果")
private Integer verifyResult;
@ExcelProperty(value = "逻辑删除")
private Integer isDeleted;
@ExcelProperty(value = "创建人ID")
private Long createBy;
@ExcelProperty(value = "更新人ID")
private Long updateBy;
@ExcelProperty(value = "创建时间")
private Date createTime;
@ExcelProperty(value = "更新时间")
private Date updateTime;
}

54
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoOperationAuditVo.java

@ -0,0 +1,54 @@
package org.dromara.system.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.system.domain.bean.CryptoOperationAudit;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = CryptoOperationAudit.class)
public class CryptoOperationAuditVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "主键ID")
private Long id;
@ExcelProperty(value = "关联敏感数据ID")
private Long sensitiveDataId;
@ExcelProperty(value = "业务表名")
private String tableName;
@ExcelProperty(value = "业务记录主键")
private String bizId;
@ExcelProperty(value = "敏感字段编码")
private String fieldCode;
@ExcelProperty(value = "操作类型")
private Integer operateType;
@ExcelProperty(value = "操作人ID")
private Long operatorId;
@ExcelProperty(value = "操作人IP")
private String operatorIp;
@ExcelProperty(value = "操作时间")
private Date operateTime;
@ExcelProperty(value = "操作备注")
private String remark;
@ExcelProperty(value = "逻辑删除")
private Integer isDeleted;
}

72
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/CryptoSensitiveDataVo.java

@ -0,0 +1,72 @@
package org.dromara.system.domain.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import org.dromara.system.domain.bean.CryptoSensitiveData;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
@Data
@ExcelIgnoreUnannotated
@AutoMapper(target = CryptoSensitiveData.class)
public class CryptoSensitiveDataVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@ExcelProperty(value = "主键ID")
private Long id;
@ExcelProperty(value = "业务表名")
private String tableName;
@ExcelProperty(value = "业务表主键ID")
private String bizId;
@ExcelProperty(value = "业务字段编码")
private String fieldCode;
@ExcelProperty(value = "字段原始值")
private String fieldValue;
@ExcelProperty(value = "SM4-CBC密文")
private String cipherText;
@ExcelProperty(value = "SM4-CBC初始化向量")
private String fieldIv;
@ExcelProperty(value = "明文SM3哈希")
private String plainHash;
@ExcelProperty(value = "哈希盐值")
private String fieldSalt;
@ExcelProperty(value = "加密算法")
private String encryptAlg;
@ExcelProperty(value = "密钥版本号")
private String keyVersion;
@ExcelProperty(value = "是否有效密文")
private Integer isActive;
@ExcelProperty(value = "逻辑删除")
private Integer isDeleted;
@ExcelProperty(value = "创建人ID")
private Long createBy;
@ExcelProperty(value = "更新人ID")
private Long updateBy;
@ExcelProperty(value = "创建时间")
private Date createTime;
@ExcelProperty(value = "更新时间")
private Date updateTime;
}

18
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/SymmetryEncryptVo.java

@ -0,0 +1,18 @@
package org.dromara.system.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Data
public class SymmetryEncryptVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private String outData;
private String ivData;
}

24
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/domain/vo/SysUserVo.java

@ -65,7 +65,7 @@ public class SysUserVo implements Serializable {
private String email;
/**
* 手机号码
* 手号码
*/
@Sensitive(strategy = SensitiveStrategy.PHONE, perms = "system:user:edit")
private String phonenumber;
@ -87,6 +87,28 @@ public class SysUserVo implements Serializable {
@JsonProperty
private String password;
/** 证件号码 */
@JsonIgnore
private String certificateNumber;
/**
* 密码备份字段(HSM 密文替换前的原值)
*/
@JsonIgnore
private String cryptoRemarkOne;
/**
* 手机号备份字段(HSM 密文替换前的原值)
*/
@JsonIgnore
private String cryptoRemarkTwo;
/**
* 证件号备份字段(HSM 密文替换前的原值)
*/
@JsonIgnore
private String cryptoRemarkThree;
/**
* 盐加密
*/

71
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/listener/SysUserImportListener.java

@ -2,7 +2,8 @@ package org.dromara.system.listener;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.http.HtmlUtil;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener;
@ -11,6 +12,7 @@ import jakarta.validation.ConstraintViolationException;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.excel.core.ExcelListener;
import org.dromara.common.excel.core.ExcelResult;
@ -18,6 +20,7 @@ import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.domain.vo.SysUserImportVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.system.service.ISysConfigService;
import org.dromara.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;
@ -34,7 +37,7 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
private final ISysUserService userService;
private final String password;
private final String initPassword;
private final Boolean isUpdateSupport;
@ -48,7 +51,7 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
public SysUserImportListener(Boolean isUpdateSupport) {
String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword");
this.userService = SpringUtils.getBean(ISysUserService.class);
this.password = BCrypt.hashpw(initPassword);
this.initPassword = initPassword;
this.isUpdateSupport = isUpdateSupport;
this.operUserId = LoginHelper.getUserId();
}
@ -57,13 +60,36 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
public void invoke(SysUserImportVo userVo, AnalysisContext context) {
SysUserVo sysUser = this.userService.selectUserByUserName(userVo.getUserName());
try {
// 验证是否存在这个用户
ICryptoSensitiveHsmService hsmService = SpringUtils.getBean(ICryptoSensitiveHsmService.class);
if (ObjectUtil.isNull(sysUser)) {
SysUserBo user = BeanUtil.toBean(userVo, SysUserBo.class);
ValidatorUtils.validate(user);
user.setPassword(password);
user.randomSalt();
String loginAccount = StringUtils.isNotBlank(user.getLoginName()) ? user.getLoginName() : user.getUserName();
user.setPassword(DigestUtil.md5Hex(loginAccount + initPassword + user.getSalt()));
user.setCreateBy(operUserId);
userService.insertUser(user);
String bizId = String.valueOf(user.getUserId());
try {
String passwordCipher = hsmService.encryptAndSave("sys_user", bizId, "password", user.getPassword());
if (StringUtils.isNotBlank(passwordCipher)) {
userService.resetUserPwd(user.getUserId(), passwordCipher);
}
} catch (Exception e) {
log.warn("HSM加密导入用户密码失败, userId={}", user.getUserId(), e);
}
try {
SysUserVo updatedUser = userService.selectUserById(user.getUserId());
hsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", user.getUserId(), e);
}
successNum++;
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功");
} else if (isUpdateSupport) {
@ -75,6 +101,41 @@ public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo
userService.checkUserDataScope(user.getUserId());
user.setUpdateBy(operUserId);
userService.updateUser(user);
String bizId = String.valueOf(userId);
try {
if (StringUtils.isNotBlank(user.getPhonenumber())) {
String phoneCipher = hsmService.encryptAndSave("sys_user", bizId, "phonenumber", user.getPhonenumber());
if (StringUtils.isNotBlank(phoneCipher)) {
SysUserBo phoneBo = new SysUserBo();
phoneBo.setUserId(userId);
phoneBo.setPhonenumber(phoneCipher);
userService.updateUserProfile(phoneBo);
}
}
if (StringUtils.isNotBlank(user.getCertificateNumber())) {
String certCipher = hsmService.encryptAndSave("sys_user", bizId, "certificateNumber", user.getCertificateNumber());
if (StringUtils.isNotBlank(certCipher)) {
SysUserBo certBo = new SysUserBo();
certBo.setUserId(userId);
certBo.setCertificateNumber(certCipher);
userService.updateUserById(certBo);
}
}
} catch (Exception e) {
log.warn("HSM加密导入用户敏感字段失败, userId={}", userId, e);
}
try {
SysUserVo updatedUser = userService.selectUserById(userId);
hsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
updatedUser.getPassword() != null ? updatedUser.getPassword() : "",
updatedUser.getPhonenumber() != null ? updatedUser.getPhonenumber() : "",
updatedUser.getCertificateNumber() != null ? updatedUser.getCertificateNumber() : "");
} catch (Exception e) {
log.warn("HSM完整性摘要更新失败, userId={}", userId, e);
}
successNum++;
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功");
} else {

9
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoIntegrityDataMapper.java

@ -0,0 +1,9 @@
package org.dromara.system.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.bean.CryptoIntegrityData;
import org.dromara.system.domain.vo.CryptoIntegrityDataVo;
public interface CryptoIntegrityDataMapper extends BaseMapperPlus<CryptoIntegrityData, CryptoIntegrityDataVo> {
}

10
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoOperationAuditMapper.java

@ -0,0 +1,10 @@
package org.dromara.system.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.bean.CryptoOperationAudit;
import org.dromara.system.domain.vo.CryptoOperationAuditVo;
public interface CryptoOperationAuditMapper extends BaseMapperPlus<CryptoOperationAudit, CryptoOperationAuditVo> {
}

10
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/mapper/CryptoSensitiveDataMapper.java

@ -0,0 +1,10 @@
package org.dromara.system.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.system.domain.bean.CryptoSensitiveData;
import org.dromara.system.domain.vo.CryptoSensitiveDataVo;
public interface CryptoSensitiveDataMapper extends BaseMapperPlus<CryptoSensitiveData, CryptoSensitiveDataVo> {
}

13
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<SysOperLog, SysOperLogVo> {
/**
* 分页查询指定操作时间范围内的操作日志记录(用于完整性摘要同步)
*
* @param page 分页参数
* @param beginTime 开始时间(含)
* @param endTime 结束时间(含)
* @return 操作日志分页记录
*/
Page<SysOperLog> selectByOperTimeRange(Page<SysOperLog> page, @Param("beginTime") Date beginTime, @Param("endTime") Date endTime);
}

11
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<SysUser, SysUserVo> {
List<SysUserVo> queryUserByIds(String[] ids);
Page<SysUserVo> queryUsersByPage(@Param("page") Page<SysUserVo> page, @Param("user") SysUserBo user);
/**
* 分页查询指定更新时间范围内的用户表记录(用于完整性摘要同步)
*
* @param page 分页参数
* @param beginTime 开始时间(含)
* @param endTime 结束时间(含)
* @return 用户分页记录
*/
Page<SysUser> selectModifiedUserByUpdateTime(Page<SysUser> page, @Param("beginTime") Date beginTime, @Param("endTime") Date endTime);
}

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

@ -0,0 +1,136 @@
package org.dromara.system.service;
import java.util.Map;
/**
* 密评敏感字段 HSM 加密服务
* <p>
* 通过 HSM 密码服务平台对敏感字段进行加密存储,满足密评合规要求。
* 手机号/身份证号使用 SM4-CBC 对称加密,密码使用 HMAC-SM3 单向哈希。
*/
public interface ICryptoSensitiveHsmService {
/**
* 加密并存储敏感字段(SM4-CBC 对称加密)
*
* @param tableName 业务表名,如 sys_user
* @param bizId 业务记录主键
* @param fieldCode 字段编码,如 phone / id_card
* @param plainText 明文值
* @return HSM 加密后的密文(cipherText),加密失败或明文为空时返回 null
*/
String encryptAndSave(String tableName, String bizId, String fieldCode, String plainText);
/**
* 哈希并存储密码(HMAC-SM3 单向哈希)
* <p>
* 生成随机 salt,调用 HSM /hsm/api/HmacWithIndex 接口计算哈希,
* 哈希值存入 plain_hash,salt 存入 field_salt,cipher_text 为空。
*
* @param tableName 业务表名
* @param bizId 业务记录主键
* @param plainPassword 明文密码
* @return 生成的盐值(Hex),供登录校验时使用
*/
String hashAndSavePassword(String tableName, String bizId, String plainPassword);
/**
* 验证密码是否匹配
*
* @param tableName 业务表名
* @param bizId 业务记录主键
* @param plainPassword 明文密码
* @return true=匹配
*/
boolean verifyPassword(String tableName, String bizId, String plainPassword);
/**
* 解密获取明文(SM4-CBC 对称解密)
*
* @param tableName 业务表名
* @param bizId 业务记录主键
* @param fieldCode 字段编码
* @return 明文值,不存在则返回 null
*/
String decryptField(String tableName, String bizId, String fieldCode);
/**
* 计算并存储数据完整性摘要(HMAC-SM3)
* <p>
* 将多个业务字段明文拼接后,调用 HSM HMAC-SM3 计算摘要,
* 结果存入 crypto_integrity_data 表,用于后续完整性校验。
*
* @param tableName 业务表名,如 sys_user
* @param bizId 业务记录主键
* @param fieldList 参与摘要的字段列表,逗号分隔,如 password,phonenumber,certificateNumber
* @param plainValues 与 fieldList 顺序一致的明文值
*/
void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues);
/**
* 同步前一天修改过的 sys_user 数据完整性摘要
* <p>
* 查询 sys_user 表中 update_time 落在前一天范围内的记录,
* 以数据库中实际存储值(password、phonenumber、certificateNumber)重新计算 HMAC-SM3 摘要,
* 按 upsert 策略写入 crypto_integrity_data 表,供后续完整性校验比对。
*
* @return 成功处理的记录数
*/
int syncIntegrityHashModifiedUsers();
/**
* 同步前一天产生的 sys_oper_log 数据完整性摘要
* <p>
* 查询 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)
* <p>
* 查询 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);
/**
* 初始化 sys_user 历史存量数据到 crypto_sensitive_data 表
* <p>
* 分页遍历 sys_user(del_flag='0')历史存量数据,对 password、phonenumber、certificateNumber
* 三个敏感字段做幂等初始化:
* <ul>
* <li>password:sys_user 中存储的 MD5 值直接作为待加密内容(口径与 SynUserInfoJob 一致)</li>
* <li>phonenumber / certificateNumber:纯数字等明文形态直接加密;AES 密文先解密为明文再 HSM 加密;
* 已是 HSM 密文或无法识别的数据跳过</li>
* <li>crypto_sensitive_data 中已存在活跃记录(tableName+bizId+fieldCode)的字段跳过,保证幂等可重复执行</li>
* </ul>
*
* @param userName 账号过滤条件,为空则初始化全部存量用户
* @return 统计信息:total=扫描用户数、success=成功初始化字段数、skip=跳过字段数、fail=失败字段数
*/
Map<String, Integer> initSysUserSensitiveData(String loginName);
/**
* 初始化 sys_user 历史存量数据到 crypto_integrity_data 表
* <p>
* 分页遍历 sys_user(del_flag='0')存量数据,对每个用户按
* password、phonenumber、certificateNumber 三个字段(以 sys_user 当前存储值为准)
* 计算 HMAC-SM3 摘要,按 upsert 策略写入 crypto_integrity_data 表。
* 已存在摘要记录的用户跳过,保证幂等可重复执行。
* 摘要口径与 verifyIntegrityHash 校验链路(verifySysUserIntegrity)保持一致。
*
* @param loginName 账号过滤条件,为空则初始化全部存量用户
* @return 统计信息:total=扫描用户数、success=成功生成摘要数、skip=已有摘要跳过数、fail=失败数
*/
Map<String, Integer> initSysUserIntegrityData(String loginName);
}

81
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/IPasswordPartyService.java

@ -0,0 +1,81 @@
package org.dromara.system.service;
import org.dromara.system.domain.vo.SymmetryEncryptVo;
/**
* 密码服务平台
*
* @author vincent
* @date 2026-09-22
*/
public interface IPasswordPartyService {
/**
* HSM - 生成随机数
*
* @param length 随机数长度
* @return 随机数(Hex 16进制类型)
*/
String generateRandom(int length);
/**
* HSM - 对称加密
*
* @param inputData 加密原文(Hex 16进制)
* @return 对称加密结果
*/
SymmetryEncryptVo symmetryEncrypt(String inputData);
/**
* HSM - 对称解密
*
* @param iv IV值,CBC/CFB/OFB模式时必传(Hex 16进制)
* @param inputData 解密密文(Hex 16进制)
* @return 对称解密结果
*/
SymmetryEncryptVo symmetryDecrypt(String iv, String inputData);
/**
* HSM - SM2签名
*
* @param plainData 明文摘要(先使用杂凑接口计算摘要,Hex 16进制类型)
* @return 签名值(Hex 16进制类型)
*/
String sm2SignData(String plainData);
/**
* HSM - SM2验签
*
* @param publicKey 公钥(使用导出公钥接口获取,Hex 16进制类型)
* @param signValue 签名值(Hex 16进制类型)
* @param plainData 明文摘要(先使用杂凑接口计算摘要,Hex 16进制类型)
* @return true=验证通过, false=验证失败
*/
boolean sm2Verify(String publicKey, String signValue, String plainData);
/**
* HSM - 非对称加密
*
* @param publcKey 公钥(Hex 16进制类型)
* @param data 明文(Hex 16进制类型)
* @return 加密密文(Hex 16进制类型)
*/
String externalEncrypt(String publcKey, String data);
/**
* HSM - 非对称解密
*
* @param enc 密文(Hex 16进制类型)
* @return 解密明文(Hex 16进制类型)
*/
String internalDecrypt(String enc);
/**
* HSM - HMAC摘要
*
* @param inputData 明文摘要(Hex 16进制类型,必填)
* @return HMAC值(Hex 16进制类型)
*/
String hmacWithIndex(String inputData);
}

8
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/ISysUserService.java

@ -173,6 +173,14 @@ public interface ISysUserService {
*/
int updateUser(SysUserBo user);
/**
* 仅更新用户指定字段(不处理角色/岗位关联)
*
* @param user 用户信息(仅更新非空字段)
* @return 结果
*/
int updateUserById(SysUserBo user);
/**
* 用户授权角色
*

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

@ -0,0 +1,641 @@
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 cn.hutool.crypto.SecureUtil;
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;
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.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
@Service
public class CryptoSensitiveHsmServiceImpl implements ICryptoSensitiveHsmService {
private final IPasswordPartyService passwordPartyService;
private final CryptoSensitiveDataMapper cryptoSensitiveDataMapper;
private final CryptoOperationAuditMapper cryptoOperationAuditMapper;
private final CryptoIntegrityDataMapper cryptoIntegrityDataMapper;
private final SysUserMapper sysUserMapper;
private final SysOperLogMapper sysOperLogMapper;
@Value("${srnwg.secretKey}")
private String secretKey;
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)
public String encryptAndSave(String tableName, String bizId, String fieldCode, String plainText) {
try{
if (StrUtil.isBlank(plainText)) {
return null;
}
String inputHex = HexUtil.encodeHexStr(plainText.getBytes(StandardCharsets.UTF_8));
SymmetryEncryptVo encryptResult = passwordPartyService.symmetryEncrypt(inputHex);
CryptoSensitiveData existing = queryActiveRecord(tableName, bizId, fieldCode);
if (existing != null) {
existing.setCipherText(encryptResult.getOutData());
existing.setFieldIv(encryptResult.getIvData());
existing.setFieldValue(plainText);
existing.setEncryptAlg("SM4-CBC");
existing.setKeyVersion(KEY_VERSION);
cryptoSensitiveDataMapper.updateById(existing);
saveAuditRecord(tableName, bizId, fieldCode, existing.getId(), 3, "HSM SM4-CBC 加密更新");
} else {
CryptoSensitiveData record = new CryptoSensitiveData();
record.setTableName(tableName);
record.setBizId(bizId);
record.setFieldCode(fieldCode);
record.setFieldValue(plainText);
record.setCipherText(encryptResult.getOutData());
record.setFieldIv(encryptResult.getIvData());
record.setEncryptAlg("SM4-CBC");
record.setKeyVersion(KEY_VERSION);
record.setIsActive(1);
record.setIsDeleted(0);
cryptoSensitiveDataMapper.insert(record);
saveAuditRecord(tableName, bizId, fieldCode, record.getId(), 1, "HSM SM4-CBC 加密存储");
}
return encryptResult.getOutData();
} catch (Exception e) {
log.error("HSM加密保存失败, tableName={}, bizId={}, fieldCode={}", tableName, bizId, fieldCode, e);
}
return "";
}
@Override
@Transactional(rollbackFor = Exception.class)
public String hashAndSavePassword(String tableName, String bizId, String plainPassword) {
if (StrUtil.isBlank(plainPassword)) {
return null;
}
return encryptAndSave(tableName, bizId, "password", plainPassword);
}
@Override
public boolean verifyPassword(String tableName, String bizId, String plainPassword) {
CryptoSensitiveData record = queryActiveRecord(tableName, bizId, "password");
if (record == null || StrUtil.isBlank(record.getCipherText()) || StrUtil.isBlank(record.getFieldIv())) {
return false;
}
try {
SymmetryEncryptVo decryptResult = passwordPartyService.symmetryDecrypt(record.getFieldIv(), record.getCipherText());
byte[] plainBytes = hexToBytes(decryptResult.getOutData());
String decryptedPassword = new String(plainBytes, StandardCharsets.UTF_8);
return decryptedPassword.equals(plainPassword);
} catch (Exception e) {
log.error("HSM解密密码验证失败, tableName={}, bizId={}", tableName, bizId, e);
return false;
}
}
@Override
public String decryptField(String tableName, String bizId, String fieldCode) {
CryptoSensitiveData record = queryActiveRecord(tableName, bizId, fieldCode);
if (record == null || StrUtil.isBlank(record.getCipherText())) {
return null;
}
SymmetryEncryptVo decryptResult = passwordPartyService.symmetryDecrypt(record.getFieldIv(), record.getCipherText());
byte[] plainBytes = hexToBytes(decryptResult.getOutData());
return new String(plainBytes, StandardCharsets.UTF_8);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) {
try {
doSaveIntegrityHash(tableName, bizId, fieldList, plainValues);
} catch (Exception e) {
log.error("保存数据完整性摘要失败, tableName={}, bizId={}", tableName, bizId, e);
}
}
/**
* 计算并 upsert 完整性摘要(异常向上抛出,供需要失败统计的调用方使用)
*/
private void doSaveIntegrityHash(String tableName, String bizId, String fieldList, String... plainValues) {
String hmacHash = calculateHash(fieldList, plainValues);
LambdaQueryWrapper<CryptoIntegrityData> 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);
}
}
@Override
public int verifyIntegrityHash(String tableName) {
try{
if (!TABLE_SYS_USER.equals(tableName) && !TABLE_SYS_OPER_LOG.equals(tableName)) {
throw new ServiceException("暂不支持该业务表的完整性校验: " + tableName);
}
Date today = DateUtil.beginOfDay(new Date());
int processed = 0;
int passed = 0;
int tampered = 0;
long pageSize = 500L;
Page<CryptoIntegrityData> page = new Page<>(1, pageSize);
LambdaQueryWrapper<CryptoIntegrityData> lqw = new LambdaQueryWrapper<CryptoIntegrityData>()
.eq(CryptoIntegrityData::getTableName, tableName)
.eq(CryptoIntegrityData::getIsDeleted, 0)
.lt(CryptoIntegrityData::getGenerateTime, today);
while (true) {
Page<CryptoIntegrityData> 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;
}catch (Exception e){
log.error("数据完整性校验失败, tableName={}", tableName, e);
}
return 0;
}
@Override
public Map<String, Integer> initSysUserSensitiveData(String loginName) {
int total = 0, success = 0, skip = 0, fail = 0;
long pageSize = 500L;
Page<SysUser> page = new Page<>(1, pageSize);
while (true) {
Page<SysUser> result = DataPermissionHelper.ignore(() -> sysUserMapper.selectPage(page,
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getDelFlag, "0")
.eq(StrUtil.isNotBlank(loginName), SysUser::getLoginName, loginName)));
List<SysUser> records = result.getRecords();
for (SysUser user : records) {
total++;
String bizId = String.valueOf(user.getUserId());
// password:存储值(MD5)直接作为 HSM 加密输入,口径与 SynUserInfoJob 一致
// 成功后:原始值备份到 crypto_remark_one,password 字段替换为 HSM 密文
int r = initOneField(user, "password", user.getPassword(), "^[0-9a-f]{32}$", false);
if (r == 1) success++; else if (r == -1) fail++; else skip++;
// phonenumber / certificateNumber:存量已是 AES 密文,AES 密文直接作为 HSM 输入(先 AES 后 HSM)
// 成功后:原始值备份到 crypto_remark_two,phonenumber 字段替换为 HSM 密文
r = initOneField(user, "phonenumber", user.getPhonenumber(), null, true);
if (r == 1) success++; else if (r == -1) fail++; else skip++;
// 成功后:原始值备份到 crypto_remark_three,certificateNumber 字段替换为 HSM 密文
r = initOneField(user, "certificateNumber", user.getCertificateNumber(), null, true);
if (r == 1) success++; else if (r == -1) fail++; else skip++;
// 备份字段 + 密文替换更新回 sys_user(须在完整性摘要之前落库,保证摘要口径与库中值一致)
updateSensitiveFields(user);
try {
doSaveIntegrityHash(TABLE_SYS_USER, bizId,
"password,phonenumber,certificateNumber",
StrUtil.nullToEmpty(user.getPassword()),
StrUtil.nullToEmpty(user.getPhonenumber()),
StrUtil.nullToEmpty(user.getCertificateNumber()));
} catch (Exception e) {
log.error("存量数据完整性摘要初始化失败, bizId={}", bizId, e);
}
}
if (!result.hasNext()) {
break;
}
page.setCurrent(page.getCurrent() + 1);
}
log.info("sys_user 存量数据初始化完成: 扫描用户数={}, 成功字段数={}, 跳过字段数={}, 失败字段数={}",
total, success, skip, fail);
Map<String, Integer> stats = new HashMap<>();
stats.put("total", total);
stats.put("success", success);
stats.put("skip", skip);
stats.put("fail", fail);
return stats;
}
@Override
public int syncIntegrityHashModifiedUsers() {
Date begin = DateUtil.beginOfDay(DateUtil.yesterday());
Date end = DateUtil.endOfDay(DateUtil.yesterday());
int processed = 0;
long pageSize = 500L;
Page<SysUser> page = new Page<>(1, pageSize);
while (true) {
Page<SysUser> 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<SysOperLog> page = new Page<>(1, pageSize);
while (true) {
Page<SysOperLog> 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;
}
@Override
public Map<String, Integer> initSysUserIntegrityData(String loginName) {
int total = 0, success = 0, skip = 0, fail = 0;
long pageSize = 500L;
Page<SysUser> page = new Page<>(1, pageSize);
while (true) {
Page<SysUser> result = DataPermissionHelper.ignore(() -> sysUserMapper.selectPage(page,
new LambdaQueryWrapper<SysUser>()
.eq(SysUser::getDelFlag, "0")
.eq(StrUtil.isNotBlank(loginName), SysUser::getLoginName, loginName)));
List<SysUser> records = result.getRecords();
for (SysUser user : records) {
total++;
String bizId = String.valueOf(user.getUserId());
if (existsIntegrityRecord(TABLE_SYS_USER, bizId)) {
skip++;
continue;
}
try {
doSaveIntegrityHash(TABLE_SYS_USER, bizId,
"password,phonenumber,certificateNumber",
StrUtil.nullToEmpty(user.getPassword()),
StrUtil.nullToEmpty(user.getPhonenumber()),
StrUtil.nullToEmpty(user.getCertificateNumber()));
success++;
} catch (Exception e) {
log.error("存量数据完整性摘要初始化失败, bizId={}", bizId, e);
fail++;
}
}
if (!result.hasNext()) {
break;
}
page.setCurrent(page.getCurrent() + 1);
}
log.info("sys_user 存量完整性摘要初始化完成: 扫描用户数={}, 成功={}, 跳过={}, 失败={}",
total, success, skip, fail);
Map<String, Integer> stats = new HashMap<>();
stats.put("total", total);
stats.put("success", success);
stats.put("skip", skip);
stats.put("fail", fail);
return stats;
}
/**
* 查询指定业务记录是否已存在完整性摘要
*/
private boolean existsIntegrityRecord(String tableName, String bizId) {
LambdaQueryWrapper<CryptoIntegrityData> lqw = new LambdaQueryWrapper<>();
lqw.eq(CryptoIntegrityData::getTableName, tableName)
.eq(CryptoIntegrityData::getBizId, bizId)
.eq(CryptoIntegrityData::getIsDeleted, 0);
return cryptoIntegrityDataMapper.selectCount(lqw) > 0;
}
/**
* 初始化单个敏感字段(幂等),成功后原始值备份到 crypto_remark_*,原字段替换为 HSM 密文
*
* @param plainPattern 明文形态兜底正则(aesCipherInput=false 时使用)
* @param aesCipherInput true=存储值为 AES 密文,AES 密文直接作为 HSM 加密输入(先 AES 后 HSM)
* @return 1=成功、0=跳过、-1=失败
*/
private int initOneField(SysUser user, String fieldCode, String storedValue,
String plainPattern, boolean aesCipherInput) {
String bizId = String.valueOf(user.getUserId());
if (StrUtil.isBlank(storedValue)) {
return 0;
}
CryptoSensitiveData existing = queryActiveRecord(TABLE_SYS_USER, bizId, fieldCode);
if (existing != null) {
// 已有密文记录(历史初始化过):直接用已有密文做备份+替换,保证重跑可补齐
if (StrUtil.isNotBlank(existing.getCipherText())) {
applyCipher(user, fieldCode, storedValue, existing.getCipherText());
return 1;
}
return 0;
}
String hsmInput;
if (aesCipherInput) {
// 双加密规则:明文 → AES 加密 → HSM 加密。
// 存量即为 AES 密文,AES 解密仅用于形态判定,解密成功则原值直接作为 HSM 输入
if (!isAesCipherText(storedValue)) {
log.warn("存量字段非AES密文形态, 跳过初始化, bizId={}, fieldCode={}", bizId, fieldCode);
return 0;
}
hsmInput = storedValue;
} else {
hsmInput = (plainPattern != null && storedValue.matches(plainPattern)) ? storedValue : null;
if (hsmInput == null) {
return 0;
}
}
try {
String cipherText = encryptAndSave(TABLE_SYS_USER, bizId, fieldCode, hsmInput);
if (StrUtil.isBlank(cipherText)) {
return -1;
}
applyCipher(user, fieldCode, storedValue, cipherText);
return 1;
} catch (Exception e) {
log.error("存量数据初始化失败, bizId={}, fieldCode={}", bizId, fieldCode, e);
return -1;
}
}
/**
* 原始值备份到 crypto_remark_*,原字段替换为 HSM 密文(仅修改内存对象,统一由 updateSensitiveFields 落库)
* password→crypto_remark_one、phonenumber→crypto_remark_two、certificateNumber→crypto_remark_three
*/
private void applyCipher(SysUser user, String fieldCode, String originalValue, String cipherText) {
switch (fieldCode) {
case "password" -> {
user.setCryptoRemarkOne(originalValue);
user.setPassword(cipherText);
}
case "phonenumber" -> {
user.setCryptoRemarkTwo(originalValue);
user.setPhonenumber(cipherText);
}
case "certificateNumber" -> {
user.setCryptoRemarkThree(originalValue);
user.setCertificateNumber(cipherText);
}
}
}
/**
* 将备份字段与密文替换结果更新回 sys_user(使用 wrapper 仅更新涉及列,不触发 update_time 自动填充)
*/
private void updateSensitiveFields(SysUser user) {
if (StrUtil.isBlank(user.getCryptoRemarkOne())
&& StrUtil.isBlank(user.getCryptoRemarkTwo())
&& StrUtil.isBlank(user.getCryptoRemarkThree())) {
return;
}
LambdaUpdateWrapper<SysUser> uw = new LambdaUpdateWrapper<>();
uw.eq(SysUser::getUserId, user.getUserId())
.set(StrUtil.isNotBlank(user.getCryptoRemarkOne()), SysUser::getCryptoRemarkOne, user.getCryptoRemarkOne())
.set(StrUtil.isNotBlank(user.getPassword()), SysUser::getPassword, user.getPassword())
.set(StrUtil.isNotBlank(user.getCryptoRemarkTwo()), SysUser::getCryptoRemarkTwo, user.getCryptoRemarkTwo())
.set(StrUtil.isNotBlank(user.getPhonenumber()), SysUser::getPhonenumber, user.getPhonenumber())
.set(StrUtil.isNotBlank(user.getCryptoRemarkThree()), SysUser::getCryptoRemarkThree, user.getCryptoRemarkThree())
.set(StrUtil.isNotBlank(user.getCertificateNumber()), SysUser::getCertificateNumber, user.getCertificateNumber());
sysUserMapper.update(null, uw);
}
/**
* 判断存储值是否为 AES 密文形态(用 srnwg.secretKey 尝试解密,成功即可解密)
*/
private boolean isAesCipherText(String value) {
try {
return StrUtil.isNotBlank(SecureUtil.aes(SecureUtil.decode(secretKey)).decryptStr(value));
} catch (Exception ignored) {
return false;
}
}
private CryptoSensitiveData queryActiveRecord(String tableName, String bizId, String fieldCode) {
LambdaQueryWrapper<CryptoSensitiveData> lqw = new LambdaQueryWrapper<>();
lqw.eq(CryptoSensitiveData::getTableName, tableName)
.eq(CryptoSensitiveData::getBizId, bizId)
.eq(CryptoSensitiveData::getFieldCode, fieldCode)
.eq(CryptoSensitiveData::getIsActive, 1)
.eq(CryptoSensitiveData::getIsDeleted, 0);
return cryptoSensitiveDataMapper.selectOne(lqw);
}
private void deactivateExisting(String tableName, String bizId, String fieldCode) {
LambdaUpdateWrapper<CryptoSensitiveData> uw = new LambdaUpdateWrapper<>();
uw.eq(CryptoSensitiveData::getTableName, tableName)
.eq(CryptoSensitiveData::getBizId, bizId)
.eq(CryptoSensitiveData::getFieldCode, fieldCode)
.eq(CryptoSensitiveData::getIsActive, 1)
.set(CryptoSensitiveData::getIsActive, 0);
cryptoSensitiveDataMapper.update(null, uw);
}
private void saveAuditRecord(String tableName, String bizId, String fieldCode,
Long sensitiveDataId, int operateType, String remark) {
CryptoOperationAudit audit = new CryptoOperationAudit();
audit.setSensitiveDataId(sensitiveDataId);
audit.setTableName(tableName);
audit.setBizId(bizId);
audit.setFieldCode(fieldCode);
audit.setOperateType(operateType);
audit.setRemark(remark);
try {
audit.setOperatorId(LoginHelper.getUserId());
} catch (Exception ignored) {
}
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];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16));
}
return data;
}
/**
* 校验 sys_user 表数据完整性
* <p>
* 根据基准摘要记录的 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 表数据完整性
* <p>
* 根据基准摘要记录的 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) : "";
}
}

267
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/PasswordPartyServiceImpl.java

@ -0,0 +1,267 @@
package org.dromara.system.service.impl;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.alibaba.fastjson.JSONObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.base.BaseException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.system.config.PasswordPartyConfig;
import org.dromara.system.domain.vo.SymmetryEncryptVo;
import org.dromara.system.service.IPasswordPartyService;
import org.springframework.stereotype.Service;
/**
* 密码服务平台
*
* @author vincent
* @date 2026-09-22
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class PasswordPartyServiceImpl implements IPasswordPartyService {
private final PasswordPartyConfig passwordPartyConfig;
private static final int KEY_INDEX = 5;
private static final String ALG_TYPE = "SM4-CBC";
/**
* 生成随机数
*
* @param length 随机数长度
* @return 随机数(Hex 16进制类型)
*/
@Override
public String generateRandom(int length) {
JSONObject params = new JSONObject();
params.put("length", length);
JSONObject data = callHsmApi("/hsm/api/GenerateRandom", params, "生成随机数");
return data.getString("random");
}
/**
* 对称加密
*
* @param inputData 加密原文(Hex 16进制类型)
* @return 对称加密结果
*/
@Override
public SymmetryEncryptVo symmetryEncrypt(String inputData) {
// SymmetryEncryptVo symmetryEncryptVo = new SymmetryEncryptVo();
// symmetryEncryptVo.setIvData("1234567890123456");
// symmetryEncryptVo.setOutData("123");
// return symmetryEncryptVo;
// TODO 生成随机数,目前使用SecureRandom,后续需要替换为HSM生成随机数
String iv = generateRandom(16);
// SecureRandom secureRandom = new SecureRandom();
// byte[] ivBytes = new byte[16];
// secureRandom.nextBytes(ivBytes);
// StringBuilder sb = new StringBuilder();
// for (byte b : ivBytes) {
// sb.append(String.format("%02x", b));
// }
// String iv = sb.toString();
JSONObject params = buildSymmetryParams(iv, inputData);
JSONObject data = callHsmApi("/hsm/api/symmetryEncrypt", params, "对称加密");
return buildSymmetryEncryptVo(data);
}
/**
* 对称解密
*
* @param iv IV值,CBC/CFB/OFB模式时必传(Hex 16进制类型)
* @param inputData 解密原文(Hex 16进制类型)
* @return 对称解密结果
*/
@Override
public SymmetryEncryptVo symmetryDecrypt(String iv, String inputData) {
JSONObject params = buildSymmetryParams(iv, inputData);
JSONObject data = callHsmApi("/hsm/api/symmetryDecrypt", params, "对称解密");
return buildSymmetryEncryptVo(data);
}
/**
* SM2签名
*
* @param plainData 明文摘要,先使用杂凑接口计算摘要(Hex 16进制类型)
* @return 签名值(Hex 16进制类型)
*/
@Override
public String sm2SignData(String plainData) {
JSONObject params = new JSONObject();
params.put("keyIndex", KEY_INDEX);
params.put("plainData", plainData);
JSONObject data = callHsmApi("/hsm/api/SM2SignData", params, "SM2签名");
return data.getString("signData");
}
/**
* SM2验签
*
* @param publicKey 公钥,使用导出公钥接口获取(Hex 16进制类型)
* @param signValue 签名值(Hex 16进制类型)
* @param plainData 明文摘要,先使用杂凑接口计算摘要(Hex 16进制类型)
* @return true=验证通过
*/
@Override
public boolean sm2Verify(String publicKey, String signValue, String plainData) {
JSONObject params = new JSONObject();
params.put("algType", ALG_TYPE);
params.put("publicKey", publicKey);
params.put("signValue", signValue);
params.put("plainData", plainData);
doPost("/hsm/api/sm2Verify", params, "SM2验签");
return true;
}
/**
* 非对称加密
*
* @param publcKey 公钥(Hex 16进制类型)
* @param data 明文(Hex 16进制类型)
* @return 加密密文(Hex 16进制类型)
*/
@Override
public String externalEncrypt(String publcKey, String data) {
JSONObject params = new JSONObject();
params.put("algType", ALG_TYPE);
params.put("publcKey", publcKey);
params.put("data", data);
JSONObject result = callHsmApi("/hsm/api/externalEncrypt", params, "非对称加密");
return result.getString("encryptData");
}
/**
* 非对称解密
*
* @param enc 密文(Hex 16进制类型)
* @return 解密明文(Hex 16进制类型)
*/
@Override
public String internalDecrypt(String enc) {
JSONObject params = new JSONObject();
params.put("keyIndex", KEY_INDEX);
params.put("algId", ALG_TYPE);
params.put("enc", enc);
JSONObject data = callHsmApi("/hsm/api/internalDecrypt", params, "非对称解密");
return data.getString("plainData");
}
/**
* HMAC摘要
*
* @param inputData 明文摘要(Hex 16进制类型,必填)
* @return HMAC值(Hex 16进制类型)
*/
@Override
public String hmacWithIndex(String inputData) {
JSONObject params = new JSONObject();
params.put("keyIndex", KEY_INDEX);
params.put("algType", "SM3");
params.put("inputData", inputData);
JSONObject data = callHsmApi("/hsm/api/HmacWithIndex", params, "HMAC");
return data.getString("hmac");
}
/**
* 构建对称加密/解密的公共请求参数
*/
private JSONObject buildSymmetryParams(String iv, String inputData) {
JSONObject params = new JSONObject();
params.put("keyIndex", KEY_INDEX);
params.put("algType", ALG_TYPE);
if (iv != null) {
params.put("iv", iv);
}
params.put("inputData", inputData);
return params;
}
/**
* 组装对称加密/解密的响应VO
*/
private SymmetryEncryptVo buildSymmetryEncryptVo(JSONObject data) {
SymmetryEncryptVo vo = new SymmetryEncryptVo();
vo.setOutData(data.getString("outData"));
vo.setIvData(data.getString("ivData"));
return vo;
}
/**
* HSM接口公共调用方法(含data非空校验)
*
* @param apiPath 接口路径
* @param params 请求参数
* @param apiName 接口名称,用于日志和异常信息
* @return 响应中的data节点
*/
private JSONObject callHsmApi(String apiPath, JSONObject params, String apiName) {
JSONObject jsonObject = doPost(apiPath, params, apiName);
JSONObject data = jsonObject.getJSONObject("data");
//TODO 测试数据
// String jsonStr = "{\"outData\":\"8216fc247b987ca99e9b5f77b375ed1a\",\"ivData\":\"123\"}";
// JSONObject data = JSONObject.parseObject(jsonStr);
if (data == null) {
throw new BaseException("HSM" + apiName + "响应data为空");
}
return data;
}
/**
* HSM接口底层HTTP调用(POST + 状态码校验 + code校验)
*
* @param apiPath 接口路径
* @param params 请求参数
* @param apiName 接口名称,用于日志和异常信息
* @return 完整响应JSON
*/
private JSONObject doPost(String apiPath, JSONObject params, String apiName) {
try {
String url = passwordPartyConfig.getBaseUrl() + apiPath;
log.info("HSM{}请求URL: {}, 参数: {}", apiName, url, params);
HttpRequest request = HttpRequest.post(url)
.header("Content-Type", "application/json;charset=utf-8")
.body(params.toJSONString())
.timeout(10000);
// 令牌配置存在时才携带鉴权头(dev 本地服务可能无鉴权)
if (StringUtils.isNotBlank(passwordPartyConfig.getAuthToken())) {
request.header("Authorization", passwordPartyConfig.getAuthToken());
}
HttpResponse response = request.execute();
if (!response.isOk()) {
response.close();
throw new BaseException("HSM" + apiName + "接口调用失败,HTTP状态码:" + response.getStatus());
}
String result = response.body();
response.close();
log.info("HSM{}响应: {}", apiName, result);
JSONObject jsonObject = JSONObject.parseObject(result);
int code = jsonObject.getIntValue("code");
if (code != 0) {
throw new BaseException("HSM" + apiName + "失败: " + jsonObject.getString("msg"));
}
return jsonObject;
} catch (BaseException e) {
throw e;
} catch (Exception e) {
log.error("HSM{}异常", apiName, e);
throw new BaseException("HSM" + apiName + "异常: " + e.getMessage());
}
}
}

1
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysJobServiceImpl.java

@ -51,6 +51,7 @@ public class SysJobServiceImpl implements ISysJobService {
for (SysJob job : jobList) {
ScheduleUtils.createScheduleJob(scheduler, job);
}
scheduler.start();
}
/**

16
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);
}
}
/**

19
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysTenantServiceImpl.java

@ -29,6 +29,7 @@ import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysTenantBo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.mapper.*;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.system.service.ISysTenantService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@ -37,6 +38,8 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import static software.amazon.awssdk.http.nio.netty.internal.ProxyTunnelInitHandler.log;
/**
* 租户Service业务层处理
*
@ -165,6 +168,22 @@ public class SysTenantServiceImpl implements ISysTenantService {
sd.setDeptId(deptId);
deptMapper.updateById(sd);
try {
String bizId = String.valueOf(user.getUserId());
String passwordCipher = SpringUtils.getBean(ICryptoSensitiveHsmService.class)
.encryptAndSave("sys_user", bizId, "password", user.getPassword());
if (StringUtils.isNotBlank(passwordCipher)) {
userMapper.update(null, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<SysUser>()
.eq(SysUser::getUserId, user.getUserId())
.set(SysUser::getPassword, passwordCipher));
}
SpringUtils.getBean(ICryptoSensitiveHsmService.class)
.saveIntegrityHash("sys_user", bizId, "password,phonenumber,certificateNumber",
passwordCipher != null ? passwordCipher : user.getPassword(), "", "");
} catch (Exception e) {
}
// 用户和角色关联表
SysUserRole userRole = new SysUserRole();
userRole.setUserId(user.getUserId());

6
zdxt-modules/zdxt-system/src/main/java/org/dromara/system/service/impl/SysUserServiceImpl.java

@ -379,6 +379,12 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
return flag;
}
@Override
public int updateUserById(SysUserBo user) {
SysUser sysUser = MapstructUtils.convert(user, SysUser.class);
return baseMapper.updateById(sysUser);
}
/**
* 用户授权角色
*

7
zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysOperLogMapper.xml

@ -4,4 +4,11 @@
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.system.mapper.SysOperLogMapper">
<select id="selectByOperTimeRange" resultType="org.dromara.system.domain.SysOperLog">
select oper_id, business_type, method, oper_name, oper_param, oper_time
from sys_oper_log
where oper_time &gt;= #{beginTime}
and oper_time &lt;= #{endTime}
</select>
</mapper>

8
zdxt-modules/zdxt-system/src/main/resources/mapper/system/SysUserMapper.xml

@ -237,4 +237,12 @@
#{id}
</foreach>
</select>
<select id="selectModifiedUserByUpdateTime" resultType="org.dromara.system.domain.SysUser">
select u.user_id, u.password, u.phonenumber, u.certificateNumber
from sys_user u
where u.del_flag = '0'
and u.update_time &gt;= #{beginTime}
and u.update_time &lt;= #{endTime}
</select>
</mapper>

17
zdxt-web-server/zdxt-admin-web-server/src/main/java/org/dromara/web/service/SysRegisterService.java

@ -3,6 +3,7 @@ package org.dromara.web.service;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.model.RegisterBody;
@ -21,6 +22,7 @@ import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.bo.SysUserBo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.system.service.ISysUserService;
import org.springframework.stereotype.Service;
@ -29,6 +31,7 @@ import org.springframework.stereotype.Service;
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class SysRegisterService {
@ -36,6 +39,7 @@ public class SysRegisterService {
private final ISysUserService userService;
private final SysUserMapper userMapper;
private final CaptchaProperties captchaProperties;
private final ICryptoSensitiveHsmService cryptoSensitiveHsmService;
/**
* 注册
@ -69,6 +73,19 @@ public class SysRegisterService {
if (!regFlag) {
throw new UserException("user.register.error");
}
try {
String bizId = String.valueOf(sysUser.getUserId());
String passwordCipher = cryptoSensitiveHsmService.encryptAndSave("sys_user", bizId, "password", sysUser.getPassword());
if (StringUtils.isNotBlank(passwordCipher)) {
userService.resetUserPwd(sysUser.getUserId(), passwordCipher);
}
cryptoSensitiveHsmService.saveIntegrityHash("sys_user", bizId,
"password,phonenumber,certificateNumber",
passwordCipher != null ? passwordCipher : (sysUser.getPassword() != null ? sysUser.getPassword() : ""),
"", "");
} catch (Exception e) {
log.error("HSM加密用户密码失败, userId={}", sysUser.getUserId(), e);
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}

16
zdxt-web-server/zdxt-admin-web-server/src/main/java/org/dromara/web/service/impl/PasswordAuthStrategy.java

@ -26,6 +26,7 @@ import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.service.ICryptoSensitiveHsmService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.PasswordService;
@ -47,6 +48,7 @@ public class PasswordAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
private final SysUserMapper userMapper;
private final PasswordService passwordService;
private final ICryptoSensitiveHsmService iCryptoSensitiveHsmService;
@Override
public LoginVo login(String body, SysClientVo client) {
@ -59,18 +61,22 @@ public class PasswordAuthStrategy implements IAuthStrategy {
String uuid = loginBody.getUuid();
boolean captchaEnabled = captchaProperties.getEnable();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
SysUser user = loadUserByUsername(username);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> {
String salt = user.getSalt();
String encodedPassword = user.getPassword();
return !passwordService.matches(password, encodedPassword, username, salt);
String bizId = String.valueOf(user.getUserId());
// try {
// return !ICryptoSensitiveHsmService.verifyPassword("sys_user", bizId, password);
// } catch (Exception e) {
log.warn("HSM密码校验失败, userId={}, 降级使用MD5校验", user.getUserId());
String salt = user.getSalt();
String encodedPassword = user.getPassword();
return !passwordService.matches(password, encodedPassword, username, salt);
// }
});
// 此处可根据登录用户的数据不同 自行创建 loginUser
return loginService.buildLoginUser(user);
});
loginUser.setClientKey(client.getClientKey());

5
zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-dev.yml

@ -347,6 +347,11 @@ srnwg:
# paasId: szsfj_zhifajianduma_001
# paasToken: ENC(0Cg8Vtpay+3hwRm+vJB0utikr4dXcEguwSaxlbTWkgQ4parB8pN2rL2xrhQ8eUd5)
--- # 密码服务平台(HSM)
password-party:
base-url: http://localhost:8080
# 鉴权令牌(Authorization 请求头),无鉴权时留空
auth-token:
--- # XSS过滤配置
xss:
# 开启XSS过滤

8
zdxt-web-server/zdxt-admin-web-server/src/main/resources/application-prod.yml

@ -276,6 +276,8 @@ justauth:
client-secret: 1f7d08**********5b7**********29e
redirect-uri: ${justauth.address}/social-callback?source=gitea
--- # XSS过滤配置
xss:
# 开启XSS过滤
@ -337,6 +339,12 @@ sms:
#市监代理页面
sjView: http://10.132.108.3/sjWeb
--- # 密码服务平台(HSM)
password-party:
# base-url: http://10.134.84.40:10085
base-url: http://10.76.108.1/mmfw
auth-token: ODE4MjY3OTcyNzQ4NTc4ODE2oZp51qbVDTfiIGpqckJwEXzx7gtZnViUlowNLl0EKj0=
--- # 业务数据双向同步配置
business-data-sync:
# 是否启用同步功能

2
zdxt-web-server/zdxt-admin-web-server/src/main/resources/businessdatasyn/sys_user.json

@ -1,7 +1,7 @@
{
"configKey": "sys_user",
"description": "用户表双向同步",
"enabled": true,
"enabled": false,
"v1": {
"table": "sys_user",

Loading…
Cancel
Save