From 12778d938d55c413491400550a511b485870a737 Mon Sep 17 00:00:00 2001 From: xydkj Date: Tue, 19 May 2026 16:33:37 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=BF=80=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/MasterKeyActivateProperties.java | 6 +- .../impl/MasterKeyActivateServiceImpl.java | 365 +++++++++--------- src/main/resources/application.yml | 3 + 3 files changed, 182 insertions(+), 192 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java b/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java index e14a10a..60a7e89 100644 --- a/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java +++ b/src/main/java/com/cisd/tms/modules/mk/config/MasterKeyActivateProperties.java @@ -7,9 +7,9 @@ import org.springframework.boot.context.properties.ConfigurationProperties; public class MasterKeyActivateProperties { //todo 后续添加到配置文件 - private String activateUrl = "http://127.0.0.1:8080/device/activate"; + private String platformUrl = "http://127.0.0.1:8080/device/active"; - public String getActivateUrl() { return activateUrl; } + public String getPlatformUrl() { return platformUrl; } - public void setActivateUrl(String activateUrl) { this.activateUrl = activateUrl; } + public void setPlatformUrl(String platformUrl) { this.platformUrl = platformUrl; } } diff --git a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java index 34d92b2..ab3977d 100644 --- a/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java +++ b/src/main/java/com/cisd/tms/modules/mk/service/impl/MasterKeyActivateServiceImpl.java @@ -21,26 +21,24 @@ import com.sunyard.cisd.device.tool.DeviceFingerprint; import com.sunyard.cisd.device.tool.DeviceFingerprintService; import lombok.Data; import lombok.RequiredArgsConstructor; -import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.digests.SM3Digest; -import org.bouncycastle.crypto.params.ECDomainParameters; -import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.params.ParametersWithRandom; -import org.bouncycastle.crypto.signers.SM2Signer; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.interfaces.ECPrivateKey; -import org.bouncycastle.jce.interfaces.ECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; import org.springframework.stereotype.Service; -import java.math.BigInteger; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; -import java.security.KeyPair; -import java.security.KeyPairGenerator; +import java.security.PublicKey; import java.security.SecureRandom; import java.security.Security; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Optional; @@ -48,6 +46,10 @@ import java.util.Optional; @RequiredArgsConstructor public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { + private static final String DEVICE_DIR = "/home/tms/device"; + public static final String SIGN_FILE = DEVICE_DIR + "/dev.sign"; + private static final String ACTIVATION_PUBLIC_KEY_FILE = DEVICE_DIR + "/dev.active.pub"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @@ -59,6 +61,9 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { private final DeviceService deviceService; private final MasterKeyActivateRepository masterKeyActivateRepository; + private static final String PROVIDER = "BC"; + private static final String SIGNATURE_ALGO = "SM3withSM2"; + static { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); @@ -127,90 +132,67 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { DeviceFingerprint deviceFingerprint = deviceFingerprintService.getDeviceFingerprint(); String deviceSerial = deviceFingerprint.getDeviceSerialNumber(); - String motherboardSerial = deviceFingerprint.getMotherboardSerialNumber(); - String cpuModel = deviceFingerprint.getCpuModel(); - String cpuSerial = deviceFingerprint.getCpuSerialNumber(); - String memorySize = deviceFingerprint.getMemorySize(); - String macAddress = deviceFingerprint.getFirstNetworkMacAddress(); - String diskSerial = deviceFingerprint.getHardDiskSerialNumber(); String timestamp = String.valueOf(System.currentTimeMillis()); String nonce = randomHex(16); - //todo 后面读取文件 - MasterKeyActivateServiceImpl.Sm2KeyPair sm2KeyPair = generateSm2KeyPair(); - - String deviceFingerprintSource = - deviceSerial + - motherboardSerial + - cpuModel + - cpuSerial + - memorySize + - macAddress + - diskSerial; - - String deviceFingerprintSm2 = sm2SignBase64( - deviceFingerprintSource.getBytes(StandardCharsets.UTF_8), - sm2KeyPair.privateKey - ); - + byte[] deviceFingerprintBytes = readFileBytes(SIGN_FILE); + String deviceFingerprintJson = Base64.getEncoder().encodeToString(deviceFingerprintBytes); String userId = trim(masterKeyActivateRequest.getUserId()); String credential = trim(masterKeyActivateRequest.getCredential()); String activationCode = trim(masterKeyActivateRequest.getActivationCode()); - - String hashSource = userId + credential + activationCode + deviceSerial + deviceFingerprint + timestamp + nonce; String hash = sm3Hex(hashSource); Params params = new Params(); - params.userId = userId; - params.credential = credential; - params.activationCode = activationCode; - params.deviceSerial = deviceSerial; - params.deviceFingerprint = deviceFingerprintSm2; - params.timestamp = timestamp; - params.nonce = nonce; + params.setUserId(userId); + params.setCredential(credential); + params.setDeviceActivationCode(activationCode); + params.setDeviceSerialNumber(deviceSerial); + params.setDeviceFingerprintJson(deviceFingerprintJson); + params.setTimestamp(timestamp); + params.setNonce(nonce); ActivateRequest request = new ActivateRequest(); - request.params = params; - request.hash = hash; + request.setParams(params); + request.setHash(hash); - //todo 先写死后续改实际的激活服务 - masterKeyActivateEntity.setActivationStatus(true); - masterKeyActivateRepository.update(masterKeyActivateEntity); -// String json = OBJECT_MAPPER.writeValueAsString(request); -//// System.out.print(json); -// HttpRequest httpRequest = HttpRequest.newBuilder() -// .uri(URI.create(masterKeyActivateProperties.getActivateUrl())) -// .header("Content-Type", "application/json;charset=UTF-8") -// .header("Accept", "application/json") -// .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) -// .build(); -// -// HttpClient client = HttpClient.newHttpClient(); -// -// HttpResponse response = client.send( -// httpRequest, -// HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) -// ); -// -// ServerResponse result = -// OBJECT_MAPPER.readValue(response.body(), ServerResponse.class); -// -// if ("ok".equals(result.status)) { -// MasterKeyActivateData dataResponse = result.getData(); -// String inputHash = dataResponse.getInputHash(); -// String deviceActivationPublicKey = dataResponse.getDeviceActivationPublicKey(); -// String activationResult = dataResponse.getActivationResult(); -// -// } else { -// throw new Exception(result.getMsg()); -// } + String json = OBJECT_MAPPER.writeValueAsString(request); +// System.out.print(json); + HttpRequest httpRequest = HttpRequest.newBuilder() + .uri(URI.create(masterKeyActivateProperties.getPlatformUrl())) + .header("Content-Type", "application/json;charset=UTF-8") + .header("Accept", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) + .build(); + + HttpClient client = HttpClient.newHttpClient(); + + HttpResponse response = client.send( + httpRequest, + HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8) + ); + + ServerResponse result = + OBJECT_MAPPER.readValue(response.body(), ServerResponse.class); + + if ("ok".equals(result.status)) { + MasterKeyActivateData dataResponse = result.getData(); + String Hash = dataResponse.getHash(); + String deviceActivationPublicKey = dataResponse.getDeviceActivationPublicKey(); + String deviceActivationResult = dataResponse.getDeviceActivationResult(); + verifySm2Signature(Hash, deviceActivationResult, deviceActivationPublicKey); + masterKeyActivateEntity.setActivationStatus(true); + masterKeyActivateRepository.update(masterKeyActivateEntity); + writeFile(ACTIVATION_PUBLIC_KEY_FILE, deviceActivationPublicKey.getBytes(StandardCharsets.UTF_8)); + } else { + throw new Exception(result.getMsg()); + } } catch (Exception e) { masterKeyActivateEntity.setActivationStatus(false); @@ -220,63 +202,6 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { } - - - private String sm2SignBase64( - byte[] data, - ECPrivateKeyParameters privateKey - ) throws Exception { - - SM2Signer signer = new SM2Signer(new SM3Digest()); - - CipherParameters parameters = new ParametersWithRandom(privateKey, SECURE_RANDOM); - - signer.init(true, parameters); - signer.update(data, 0, data.length); - - byte[] signature = signer.generateSignature(); - - return Base64.getEncoder().encodeToString(signature); - } - - - private Sm2KeyPair generateSm2KeyPair(){ - try{ - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC"); - ECParameterSpec sm2Spec = ECNamedCurveTable.getParameterSpec("sm2p256v1"); - keyPairGenerator.initialize(sm2Spec, SECURE_RANDOM); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - - ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate(); - ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); - BigInteger d = privateKey.getD(); - - ECDomainParameters domainParameters = new ECDomainParameters( - sm2Spec.getCurve(), - sm2Spec.getG(), - sm2Spec.getN(), - sm2Spec.getH() - ); - - ECPrivateKeyParameters privateKeyParameters = - new ECPrivateKeyParameters(d, domainParameters); - - ECPublicKeyParameters publicParameters = - new ECPublicKeyParameters(publicKey.getQ(), domainParameters); - - - Sm2KeyPair sm2KeyPair = new Sm2KeyPair(); - sm2KeyPair.privateKey = privateKeyParameters; - sm2KeyPair.publicKeyParameters = publicParameters; - sm2KeyPair.publicKey = publicKey; - - return sm2KeyPair; - } catch (Exception e){ - throw new BizException(ErrorCode.BIZ_ERROR.getCode(), "sm2密钥对生成失败"); - } - - } - private String sm3Hex(String input) { byte[] data = input.getBytes(StandardCharsets.UTF_8); @@ -306,80 +231,49 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { } - private boolean sm2VerifyActivationResult( - String inputHash, - String activationResult, - ECPublicKeyParameters publicKeyParameters - ) { + public boolean verifySm2Signature(String data, String signatureBase64, String publicKeyBase64) { try { + byte[] keyBytes = Base64.getDecoder().decode(publicKeyBase64); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + java.security.KeyFactory keyFactory = java.security.KeyFactory.getInstance("EC", PROVIDER); + PublicKey publicKey = keyFactory.generatePublic(keySpec); - byte[] signatureBytes = parseP1Signature(activationResult); - - SM2Signer signer = new SM2Signer(new SM3Digest()); - - - signer.init(false, publicKeyParameters); - - byte[] data = inputHash.getBytes(StandardCharsets.UTF_8); - - signer.update(data, 0, data.length); - - return signer.verifySignature(signatureBytes); - + Signature signature = Signature.getInstance(SIGNATURE_ALGO, PROVIDER); + signature.initVerify(publicKey); + signature.update(data.getBytes(StandardCharsets.UTF_8)); + byte[] signatureBytes = Base64.getDecoder().decode(signatureBase64); + return signature.verify(signatureBytes); } catch (Exception e) { - e.printStackTrace(); - return false; + throw new RuntimeException("SM2验签失败", e); } } - private byte[] parseP1Signature(String activationResult) { - String value = activationResult.trim(); - if (value.startsWith("P1:")) { - value = value.substring(3); - } - return Base64.getDecoder().decode(value); - } + + + @Data public static class ActivateRequest { - public Params params; - public String hash; - } - - public static class Params { - - public String userId; - public String credential; - public String activationCode; - public String deviceSerial; - public String deviceFingerprint; - public String timestamp; - public String nonce; + private Params params; + private String hash; } @JsonIgnoreProperties(ignoreUnknown = true) @Data public static class ServerResponse { - public String status; - public String msg; - public Integer code; - public MasterKeyActivateData data; + private String status; + private String msg; + private Integer code; + private MasterKeyActivateData data; } @JsonIgnoreProperties(ignoreUnknown = true) @Data public static class MasterKeyActivateData { - - public String inputHash; - public String deviceActivationPublicKey; - public String activationResult; - } - - private static class Sm2KeyPair { - private ECPrivateKeyParameters privateKey; - private ECPublicKeyParameters publicKeyParameters; - private ECPublicKey publicKey; + private String hash; + private String deviceActivationPublicKey; + private String deviceActivationResult; } private String loadTmsVersion() { @@ -387,7 +281,100 @@ public class MasterKeyActivateServiceImpl implements MasterKeyActivateService { return version.map(DeviceSoftwareVersionEntity::getCurrentVersion).map(MasterKeyActivateServiceImpl::trim).orElse(""); } + + public static byte[] readFileBytes(String filePath) throws Exception { + File file = new File(filePath); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] content = new byte[(int) file.length()]; + fis.read(content); + return content; + } + } + + public static void writeFile(String filePath, byte[] content) throws Exception { + File file = new File(filePath); + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + try (OutputStream os = new FileOutputStream(file)) { + os.write(content); + } + } + + private static String trim(String value) { return value == null ? "" : value.trim(); } + + + + public static class Params { + private String userId; + private String credential; + private String deviceActivationCode; + private String deviceSerialNumber; + private String deviceFingerprintJson; + private String timestamp; + private String nonce; + + public Params() { + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getCredential() { + return credential; + } + + public void setCredential(String credential) { + this.credential = credential; + } + + public String getDeviceActivationCode() { + return deviceActivationCode; + } + + public void setDeviceActivationCode(String deviceActivationCode) { + this.deviceActivationCode = deviceActivationCode; + } + + public String getDeviceSerialNumber() { + return deviceSerialNumber; + } + + public void setDeviceSerialNumber(String deviceSerialNumber) { + this.deviceSerialNumber = deviceSerialNumber; + } + + public String getDeviceFingerprintJson() { + return deviceFingerprintJson; + } + + public void setDeviceFingerprintJson(String deviceFingerprintJson) { + this.deviceFingerprintJson = deviceFingerprintJson; + } + + public String getTimestamp() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public String getNonce() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index aec6974..bafdba8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -335,6 +335,9 @@ tms: old-pin-base64: ${TMS_MK_INIT_IDENTIFY_OLD_PIN_BASE64:NjY2NjY2} # SDFE_InitIdentify 新 PIN(Base64 编码);主密钥初始化接口会读取该值。 new-pin-base64: ${TMS_MK_INIT_IDENTIFY_NEW_PIN_BASE64:NjY2NjY2} + activate: + # 激活服务平台路径 + platform-url: ${TMS_MK_ACTIVATE_PLATFORM_URL:http://127.0.0.1:8080/device/active} security: # 内部接口鉴权 token(前后端内部调用需一致)。 internal-token: ${TMS_INTERNAL_TOKEN:change-me-internal-token} From d860bc078afb8b93015fc327f3a20c03bae99051 Mon Sep 17 00:00:00 2001 From: cheney Date: Mon, 18 May 2026 16:46:51 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=AF=B9=E8=B1=A1=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E6=97=B6=E8=AE=A1=E7=AE=97=20mac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 +++ .../modules/datamac/entity/DataMacEntity.java | 12 +++++ .../hanlder/DataMacMetaObjectHandler.java | 45 +++++++++++++++++++ .../datamac/helper/Sm3MacCalculator.java | 39 ++++++++++++++++ .../interceptor/MacValidationInterceptor.java | 42 +++++++++++++++++ 5 files changed, 145 insertions(+) create mode 100644 src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java create mode 100644 src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java create mode 100644 src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java create mode 100644 src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java diff --git a/pom.xml b/pom.xml index 4b97e1a..d9f6468 100644 --- a/pom.xml +++ b/pom.xml @@ -118,6 +118,13 @@ 1.10.0 + + cn.hutool + hutool-all + 5.8.26 + + + com.sunyard.cisd device-fingerprint-tool diff --git a/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java b/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java new file mode 100644 index 0000000..9611ab5 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/entity/DataMacEntity.java @@ -0,0 +1,12 @@ +package com.cisd.tms.modules.datamac.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +@Data +public class DataMacEntity { + // 标记此字段在插入和更新时都需要自动填充 + @TableField(fill = FieldFill.INSERT_UPDATE) + private String mac; +} \ No newline at end of file diff --git a/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java b/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java new file mode 100644 index 0000000..a0038d9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/hanlder/DataMacMetaObjectHandler.java @@ -0,0 +1,45 @@ +package com.cisd.tms.modules.datamac.hanlder; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +@Component +public class DataMacMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + // 插入时,直接基于当前实体对象计算 MAC + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } + + @Override + public void updateFill(MetaObject metaObject) { + + } + +// @Override +// public void updateFill(MetaObject metaObject) { +// // 更新时,需要先获取 ID,再查询完整数据 +// Object entity = metaObject.getOriginalObject(); +// Object id = this.getFieldValByName("id", metaObject); +// if (id == null) { +// // 若没有 ID,则降级使用当前对象中的字段(不完整) +// String mac = Sm3MacCalculator.calculateForEntity(entity); +// this.strictUpdateFill(metaObject, "mac", String.class, mac); +// return; +// } +// +// // 通过 AOP 或手动调用 Service,在此处获取完整实体 +// Object fullEntity = fetchFullEntityFromService(entity.getClass(), id); +// if (fullEntity != null) { +// // 将当前更新内容合并到完整实体 +// mergeChanges(fullEntity, entity); +// String mac = Sm3MacCalculator.calculateForEntity(fullEntity); +// this.strictUpdateFill(metaObject, "mac", String.class, mac); +// } +// } +} diff --git a/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java b/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java new file mode 100644 index 0000000..9787377 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/helper/Sm3MacCalculator.java @@ -0,0 +1,39 @@ +package com.cisd.tms.modules.datamac.helper; +import cn.hutool.crypto.SmUtil; +import cn.hutool.core.util.ArrayUtil; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; + + + +public class Sm3MacCalculator { + public static String calculateForEntity(Object entity) { + // 1. 获取所有字段并按字段名排序,保证不同实体计算的顺序一致 + Field[] fields = entity.getClass().getDeclaredFields(); + Arrays.sort(fields, Comparator.comparing(Field::getName)); + + // 2. 构建待摘要的数据 + StringBuilder dataBuilder = new StringBuilder(); + for (Field field : fields) { + // 排除 mac 字段自身 + if ("mac".equals(field.getName())) { + continue; + } + try { + field.setAccessible(true); + Object value = field.get(entity); + dataBuilder.append(field.getName()).append("=") + .append(value != null ? value.toString() : "null") + .append(";"); + } catch (IllegalAccessException e) { + // 适当的日志记录 + throw new RuntimeException("Failed to read field: " + field.getName(), e); + } + } + + // 3. 生成最终的 SM3 哈希值 + return SmUtil.sm3(dataBuilder.toString()); + } +} diff --git a/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java b/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java new file mode 100644 index 0000000..2fb25d9 --- /dev/null +++ b/src/main/java/com/cisd/tms/modules/datamac/interceptor/MacValidationInterceptor.java @@ -0,0 +1,42 @@ +package com.cisd.tms.modules.datamac.interceptor; + + +import com.cisd.tms.modules.datamac.entity.DataMacEntity; +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; +import org.apache.ibatis.executor.resultset.ResultSetHandler; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.springframework.stereotype.Component; + +import java.sql.Statement; +import java.util.List; + + +@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})) +@Component +public class MacValidationInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + Object result = invocation.proceed(); + if (result instanceof List) { + ((List) result).forEach(this::validateEntity); + } else { + validateEntity(result); + } + return result; + } + + private void validateEntity(Object obj) { + if (obj instanceof DataMacEntity) { + DataMacEntity entity = (DataMacEntity) obj; + String storedMac = entity.getMac(); + String calculatedMac = Sm3MacCalculator.calculateForEntity(entity); + if (!storedMac.equals(calculatedMac)) { + throw new RuntimeException("Data integrity validation failed. The data has been tampered with."); + } + } + } +} \ No newline at end of file From 4f346371afe3f91b7458896b84e005521fe01442 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 19 May 2026 13:59:55 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=AE=A1=E8=AE=A1=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20mac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cisd/tms/modules/log/entity/OperationAuditLogEntity.java | 3 ++- src/main/resources/db/migration/V1__tms_schema_full.sql | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java index fe0acb8..1bb54a7 100644 --- a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java +++ b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java @@ -3,6 +3,7 @@ package com.cisd.tms.modules.log.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import com.cisd.tms.modules.datamac.entity.DataMacEntity; import com.cisd.tms.modules.log.enums.*; import lombok.Data; import lombok.experimental.Accessors; @@ -12,7 +13,7 @@ import java.time.LocalDateTime; @Data @Accessors(chain = true) @TableName("tms_operation_audit_log") -public class OperationAuditLogEntity { +public class OperationAuditLogEntity extends DataMacEntity { @TableId(type = IdType.ASSIGN_ID) private Long id; private String logId; diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index a9c6890..36d8ce4 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -706,3 +706,6 @@ INSERT IGNORE INTO tms_master_key_activate ( ever_initialized, activation_status ) VALUES (1, true, true); + + +ALTER TABLE TMS.tms_auth_audit_log ADD mac varchar(100) NULL COMMENT '完整性校验值'; From d1134fe40f8fd749092c0e4ade3a22517f97ba58 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 19 May 2026 16:06:04 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E5=AE=9E=E4=BD=93=E5=A2=9E=E5=8A=A0=20mac?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cisd/tms/TmsApplication.java | 29 +++++++++++++++++-- .../mybatis/CommonMetaObjectHandler.java | 24 +++++++++++++-- .../tms/modules/cert/entity/KeyEntity.java | 6 ++++ .../log/entity/OperationAuditLogEntity.java | 1 + .../log/service/OperationAuditService.java | 2 +- .../db/migration/V1__tms_schema_full.sql | 3 +- 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/cisd/tms/TmsApplication.java b/src/main/java/com/cisd/tms/TmsApplication.java index fb73702..e04caed 100644 --- a/src/main/java/com/cisd/tms/TmsApplication.java +++ b/src/main/java/com/cisd/tms/TmsApplication.java @@ -1,9 +1,19 @@ package com.cisd.tms; import com.cisd.tms.common.crypto.config.ConfigCryptoCli; -import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import com.cisd.tms.modules.log.dto.OperationAuditCommand; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.AuthLevel; +import com.cisd.tms.modules.log.enums.ModuleCode; +import com.cisd.tms.modules.log.enums.OperationResult; +import com.cisd.tms.modules.log.enums.OperatorRoleCode; +import com.cisd.tms.modules.log.service.OperationAuditService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @@ -11,6 +21,8 @@ import org.springframework.scheduling.annotation.EnableScheduling; @EnableScheduling public class TmsApplication { + private static final Logger log = LoggerFactory.getLogger(TmsApplication.class); + public static void main(String[] args) { if (ConfigCryptoCli.shouldHandle(args)) { int exitCode = ConfigCryptoCli.run(args, System.in, System.out, System.err); @@ -19,6 +31,19 @@ public class TmsApplication { } return; } - SpringApplication.run(TmsApplication.class, args); + ConfigurableApplicationContext context = SpringApplication.run(TmsApplication.class, args); + + log.error("=== 临时测试:调用 OperationAuditService record 方法 ==="); + OperationAuditService auditService = context.getBean(OperationAuditService.class); + OperationAuditCommand command = new OperationAuditCommand() + .setOperatorRoleCode(OperatorRoleCode.SUPER_ADMIN) + .setOperatorAuthLevel(AuthLevel.FULL) + .setModuleCode(ModuleCode.SYSTEM) + .setActionType(ActionType.EXECUTE) + .setRemoteIp("127.0.0.1") + .setOperationResult(OperationResult.SUCCESS) + .setSummary("系统启动测试 - OperationAuditService 功能验证"); + auditService.record(command, false); + log.error("=== 临时测试完成:审计日志已记录 ==="); } } diff --git a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java index 69d9f79..00c57c4 100644 --- a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java +++ b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java @@ -2,20 +2,38 @@ package com.cisd.tms.infrastructure.persistence.mybatis; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import java.time.LocalDateTime; + +import com.cisd.tms.modules.datamac.helper.Sm3MacCalculator; import org.apache.ibatis.reflection.MetaObject; +import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @Component +@Primary public class CommonMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { - this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); - this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + if ( metaObject.hasSetter("createTime") ) { + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + } + + if ( metaObject.hasSetter("updateTime") ) { + this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } + + // 插入时,直接基于当前实体对象计算 MAC + if (metaObject.hasSetter("mac")) { + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } } @Override public void updateFill(MetaObject metaObject) { - this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + if ( metaObject.hasSetter("updateTime") ) { + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); + } } } diff --git a/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java index 4afe9d2..e5bf231 100644 --- a/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java +++ b/src/main/java/com/cisd/tms/modules/cert/entity/KeyEntity.java @@ -1,5 +1,7 @@ package com.cisd.tms.modules.cert.entity; +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import com.cisd.tms.infrastructure.persistence.entity.BaseEntity; import lombok.Data; @@ -26,4 +28,8 @@ public class KeyEntity extends BaseEntity { private String allowIps; private String signData; private String createBy; + + // 标记此字段在插入和更新时都需要自动填充 + @TableField(fill = FieldFill.INSERT_UPDATE) + private String mac; } diff --git a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java index 1bb54a7..633cce5 100644 --- a/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java +++ b/src/main/java/com/cisd/tms/modules/log/entity/OperationAuditLogEntity.java @@ -1,6 +1,7 @@ package com.cisd.tms.modules.log.entity; import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.cisd.tms.modules.datamac.entity.DataMacEntity; diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index 8944688..bb09fb6 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -63,7 +63,7 @@ public class OperationAuditService { .setAuditStatus(AuditStatus.PENDING); //todo 后续测试签名 - auditSigner.signEntity(entity); +// auditSigner.signEntity(entity); operationAuditLogRepository.add(entity); diff --git a/src/main/resources/db/migration/V1__tms_schema_full.sql b/src/main/resources/db/migration/V1__tms_schema_full.sql index 36d8ce4..cb8c691 100644 --- a/src/main/resources/db/migration/V1__tms_schema_full.sql +++ b/src/main/resources/db/migration/V1__tms_schema_full.sql @@ -708,4 +708,5 @@ INSERT IGNORE INTO tms_master_key_activate ( ) VALUES (1, true, true); -ALTER TABLE TMS.tms_auth_audit_log ADD mac varchar(100) NULL COMMENT '完整性校验值'; +ALTER TABLE TMS.tms_operation_audit_log ADD mac varchar(100) NULL COMMENT '完整性校验值'; +ALTER TABLE TMS.tms_key_entity ADD mac varchar(100) NULL COMMENT '完整性校验值'; From 7504c6960e785521fc58db80c2970caa0a9b75b0 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 19 May 2026 16:10:35 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cisd/tms/TmsApplication.java | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/cisd/tms/TmsApplication.java b/src/main/java/com/cisd/tms/TmsApplication.java index e04caed..96e4325 100644 --- a/src/main/java/com/cisd/tms/TmsApplication.java +++ b/src/main/java/com/cisd/tms/TmsApplication.java @@ -1,13 +1,14 @@ package com.cisd.tms; import com.cisd.tms.common.crypto.config.ConfigCryptoCli; -import com.cisd.tms.modules.log.dto.OperationAuditCommand; +import com.cisd.tms.modules.log.entity.OperationAuditLogEntity; import com.cisd.tms.modules.log.enums.ActionType; import com.cisd.tms.modules.log.enums.AuthLevel; +import com.cisd.tms.modules.log.enums.AuditStatus; import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.modules.log.enums.OperationResult; import com.cisd.tms.modules.log.enums.OperatorRoleCode; -import com.cisd.tms.modules.log.service.OperationAuditService; +import com.cisd.tms.modules.log.repository.OperationAuditLogRepository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.SpringApplication; @@ -16,6 +17,9 @@ import org.springframework.boot.context.properties.ConfigurationPropertiesScan; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.scheduling.annotation.EnableScheduling; +import java.time.LocalDateTime; +import java.util.UUID; + @SpringBootApplication @ConfigurationPropertiesScan @EnableScheduling @@ -34,16 +38,25 @@ public class TmsApplication { ConfigurableApplicationContext context = SpringApplication.run(TmsApplication.class, args); log.error("=== 临时测试:调用 OperationAuditService record 方法 ==="); - OperationAuditService auditService = context.getBean(OperationAuditService.class); - OperationAuditCommand command = new OperationAuditCommand() + OperationAuditLogRepository repository = context.getBean(OperationAuditLogRepository.class); + + LocalDateTime now = LocalDateTime.now(); + OperationAuditLogEntity entity = new OperationAuditLogEntity(); + entity.setLogId(UUID.randomUUID().toString().replace("-", "")) .setOperatorRoleCode(OperatorRoleCode.SUPER_ADMIN) .setOperatorAuthLevel(AuthLevel.FULL) .setModuleCode(ModuleCode.SYSTEM) .setActionType(ActionType.EXECUTE) .setRemoteIp("127.0.0.1") .setOperationResult(OperationResult.SUCCESS) - .setSummary("系统启动测试 - OperationAuditService 功能验证"); - auditService.record(command, false); + .setSummary("系统启动测试 - OperationAuditService 功能验证") + .setAuditStatus(AuditStatus.PENDING) + .setOccurredAt(now) + .setCreateTime(now) + .setPayloadHash("TEST_PAYLOAD_HASH") + .setSignValue("TEST_SIGN_VALUE"); + + repository.add(entity); log.error("=== 临时测试完成:审计日志已记录 ==="); } } From 70740762af311fb89516695f0633c28b354cb6c3 Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 19 May 2026 16:11:35 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E5=9B=9E=E6=BB=9A=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/cisd/tms/TmsApplication.java | 24 +------------------ .../log/service/OperationAuditService.java | 2 +- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/cisd/tms/TmsApplication.java b/src/main/java/com/cisd/tms/TmsApplication.java index 96e4325..9764663 100644 --- a/src/main/java/com/cisd/tms/TmsApplication.java +++ b/src/main/java/com/cisd/tms/TmsApplication.java @@ -35,28 +35,6 @@ public class TmsApplication { } return; } - ConfigurableApplicationContext context = SpringApplication.run(TmsApplication.class, args); - - log.error("=== 临时测试:调用 OperationAuditService record 方法 ==="); - OperationAuditLogRepository repository = context.getBean(OperationAuditLogRepository.class); - - LocalDateTime now = LocalDateTime.now(); - OperationAuditLogEntity entity = new OperationAuditLogEntity(); - entity.setLogId(UUID.randomUUID().toString().replace("-", "")) - .setOperatorRoleCode(OperatorRoleCode.SUPER_ADMIN) - .setOperatorAuthLevel(AuthLevel.FULL) - .setModuleCode(ModuleCode.SYSTEM) - .setActionType(ActionType.EXECUTE) - .setRemoteIp("127.0.0.1") - .setOperationResult(OperationResult.SUCCESS) - .setSummary("系统启动测试 - OperationAuditService 功能验证") - .setAuditStatus(AuditStatus.PENDING) - .setOccurredAt(now) - .setCreateTime(now) - .setPayloadHash("TEST_PAYLOAD_HASH") - .setSignValue("TEST_SIGN_VALUE"); - - repository.add(entity); - log.error("=== 临时测试完成:审计日志已记录 ==="); + SpringApplication.run(TmsApplication.class, args); } } diff --git a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java index bb09fb6..8944688 100644 --- a/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java +++ b/src/main/java/com/cisd/tms/modules/log/service/OperationAuditService.java @@ -63,7 +63,7 @@ public class OperationAuditService { .setAuditStatus(AuditStatus.PENDING); //todo 后续测试签名 -// auditSigner.signEntity(entity); + auditSigner.signEntity(entity); operationAuditLogRepository.add(entity); From 132c696cf741be83ca8df4b44b7de7a43ee362eb Mon Sep 17 00:00:00 2001 From: cheney Date: Tue, 19 May 2026 16:42:48 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=97=B6=E5=A6=82?= =?UTF-8?q?=E6=9C=89=E9=9C=80=E8=A6=81=E4=B9=9F=E8=87=AA=E5=8A=A8=E6=8F=92?= =?UTF-8?q?=E5=85=A5=20mac=20=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistence/mybatis/CommonMetaObjectHandler.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java index 00c57c4..bb9f3e9 100644 --- a/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java +++ b/src/main/java/com/cisd/tms/infrastructure/persistence/mybatis/CommonMetaObjectHandler.java @@ -35,5 +35,11 @@ public class CommonMetaObjectHandler implements MetaObjectHandler { if ( metaObject.hasSetter("updateTime") ) { this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); } + // 插入时,直接基于当前实体对象计算 MAC + if (metaObject.hasSetter("mac")) { + Object entity = metaObject.getOriginalObject(); + String mac = Sm3MacCalculator.calculateForEntity(entity); + this.strictInsertFill(metaObject, "mac", String.class, mac); + } } }