From e06317c883b2d4136886f7c7240e8dead4c8d05c Mon Sep 17 00:00:00 2001 From: waner Date: Fri, 22 May 2026 16:01:26 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A=E6=97=A5=E5=BF=97=E5=92=8C?= =?UTF-8?q?=E9=98=B2=E9=87=8D=E6=94=BE=E8=A1=A5=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthAdminController.java | 2 + .../auth/controller/AuthController.java | 1 + .../controller/NetworkConfigController.java | 12 +++++ .../controller/TimeConfigController.java | 4 ++ .../file/controller/FileController.java | 4 ++ .../controller/AuditLogRestoreController.java | 8 ++- .../controller/BackupConfigController.java | 6 +++ .../log/controller/BackupController.java | 9 +++- .../controller/OperationAuditController.java | 6 +++ .../modules/mk/controller/LmkController.java | 7 +++ .../service/controller/OpenApiController.java | 10 ++++ .../upgrade/controller/UpgradeController.java | 1 + .../ReplayProtectedEndpointsTest.java | 53 +++++++++++++++---- 13 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java b/src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java index 1f9f237..2e20ed5 100644 --- a/src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java +++ b/src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java @@ -79,6 +79,7 @@ public class AuthAdminController { @PostMapping("/super-admin/ukeys/issue-sign") @Operation(summary = "签发并绑定超级管理员 UKey", description = "系统初始化后登记超级管理员 UKey,不要求已有登录态。") + @AuditedOperation(module = ModuleCode.AUTH, action = ActionType.BIND, summary = "签发并绑定超级管理员 UKey") public ApiResponse issueSuperAdminUkeyBindingSign( @Valid @RequestBody UKeySignDTO request ) { @@ -94,6 +95,7 @@ public class AuthAdminController { @Operation(summary = "签发并绑定管理员 UKey", description = "仅允许 SUPER_ADMIN FULL 会话登记或重绑定管理员 UKey;超级管理员重绑定需传 ukeyBindingId。") @ReplayProtected @RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL) + @AuditedOperation(module = ModuleCode.AUTH, action = ActionType.BIND, summary = "签发并绑定管理员 UKey") public ApiResponse issueUkeyBindingSign( @PathVariable("roleCode") String roleCode, @Valid @RequestBody UKeySignDTO request, diff --git a/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java b/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java index 815432c..ebe6964 100644 --- a/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java +++ b/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java @@ -61,6 +61,7 @@ public class AuthController { @PostMapping("/logout") @Operation(summary = "退出当前会话", description = "使当前会话失效。") @AuditedOperation(module = ModuleCode.AUTH, action = ActionType.LOGOUT, summary = "退出当前会话") + @ReplayProtected public ApiResponse logout(HttpServletRequest request) { authService.logout((String) request.getAttribute(InternalApiAuthInterceptor.ATTR_SESSION_TOKEN)); return ApiResponse.success(); diff --git a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java index eb9cfbd..b7af1fc 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java @@ -3,6 +3,9 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.device.dto.network.*; import com.cisd.tms.modules.device.entity.PageResult; import com.cisd.tms.modules.device.service.NetworkConfigService; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -37,6 +40,7 @@ public class NetworkConfigController { @Operation(summary = "配置网卡", description = "配置指定网卡的 IPv4 或 IPv6 地址、前缀长度、网关和状态") @PostMapping("/card/configure") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "配置网卡") public ApiResponse configureNetworkCard(@RequestBody @Validated NetworkConfigureRequest request) { networkConfigService.configureNetworkCard(request); return ApiResponse.success(); @@ -44,6 +48,7 @@ public class NetworkConfigController { @Operation(summary = "新增网卡配置", description = "新增或配置指定网卡的网络地址信息") @PostMapping("/card/add") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "新增网卡配置") public ApiResponse addNetworkCard(@RequestBody @Validated NetworkConfigureRequest req) { networkConfigService.configureNetworkCard(req); return ApiResponse.success(); @@ -51,6 +56,7 @@ public class NetworkConfigController { @Operation(summary = "删除网卡 IP 配置", description = "删除指定网卡下的某条 IPv4 或 IPv6 地址配置") @PostMapping("/card/delete") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除网卡IP配置") public ApiResponse deleteNetworkCardRecord(@RequestBody NetworkDeleteRequest req) { networkConfigService.deleteNetworkCardRecord(req); return ApiResponse.success(); @@ -65,6 +71,7 @@ public class NetworkConfigController { @Operation(summary = "删除 Bond", description = "删除指定 Bond 聚合接口及其从属网卡连接配置") @PostMapping("/bond/delete/{bondName}") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除Bond") public ApiResponse deleteBond(@PathVariable String bondName) { networkConfigService.deleteBond(bondName); return ApiResponse.success(); @@ -79,6 +86,7 @@ public class NetworkConfigController { @Operation(summary = "创建Bond", description = "创建一个新的Bond聚合接口,需指定名称、模式和从属接口") @PostMapping("/bond/create") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "创建Bond") public ApiResponse createBond(@Valid @RequestBody BondCreateRequest req) { networkConfigService.createBond(req); return ApiResponse.success(); @@ -86,6 +94,7 @@ public class NetworkConfigController { @Operation(summary = "配置 Bond", description = "修改指定 Bond 的模式、IP 地址、网关、从属物理网卡和激活状态") @PostMapping("/bond/config") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "配置Bond") public ApiResponse configBond(@Valid @RequestBody BondConfigRequest req) { networkConfigService.configBond(req); return ApiResponse.success(); @@ -99,6 +108,7 @@ public class NetworkConfigController { @Operation(summary = "新增路由", description = "新增一条 IPv4 或 IPv6 静态路由配置,可指定目标地址、前缀长度、网口和下一跳") @PostMapping("/route/add") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.CREATE, summary = "新增路由") public ApiResponse routeAdd(@Valid @RequestBody RouteCreateRequest req) { networkConfigService.addRoute(req); return ApiResponse.success(); @@ -106,6 +116,7 @@ public class NetworkConfigController { @Operation(summary = "修改路由", description = "修改一条已有静态路由,通过删除旧路由并新增新路由实现") @PostMapping("/route/update") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.UPDATE, summary = "修改路由") public ApiResponse routeUpdate(@Valid @RequestBody RouteUpdateRequest req) { networkConfigService.updateRoute(req); return ApiResponse.success(); @@ -113,6 +124,7 @@ public class NetworkConfigController { @Operation(summary = "删除路由", description = "删除指定网口下的一条 IPv4 或 IPv6 静态路由配置") @PostMapping("/route/delete") + @AuditedOperation(module = ModuleCode.NETWORK, action = ActionType.DELETE, summary = "删除路由") public ApiResponse routeDelete(@Valid @RequestBody RouteDeleteRequest req) { networkConfigService.deleteRoute(req); return ApiResponse.success(); diff --git a/src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java b/src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java index eb77acb..5bda5f6 100644 --- a/src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java +++ b/src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java @@ -8,6 +8,9 @@ import com.cisd.tms.modules.auth.enums.RoleCode; import com.cisd.tms.modules.auth.security.RequireInternalAuth; import com.cisd.tms.modules.device.dto.TimeConfigRequest; import com.cisd.tms.modules.device.service.TimeConfigService; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -25,6 +28,7 @@ public class TimeConfigController { private final TimeConfigService timeConfigService; @PostMapping("/time-config") + @AuditedOperation(module = ModuleCode.DEVICE, action = ActionType.UPDATE, summary = "配置系统时间") public ApiResponse configureTime(@RequestBody TimeConfigRequest request) { //todo 鉴权 timeConfigService.processTimeConfig(request); diff --git a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java index aba06ef..5ddb988 100644 --- a/src/main/java/com/cisd/tms/modules/file/controller/FileController.java +++ b/src/main/java/com/cisd/tms/modules/file/controller/FileController.java @@ -4,6 +4,9 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.file.dto.FileDetailResponse; import com.cisd.tms.modules.file.dto.FileUploadResponse; import com.cisd.tms.modules.file.service.FileService; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -28,6 +31,7 @@ public class FileController { // 内部上传入口,前端先拿到 fileId,再把 fileId 传给初始化相关接口。 @PostMapping("/upload") @ReplayProtected + @AuditedOperation(module = ModuleCode.SYSTEM, action = ActionType.IMPORT, summary = "上传内部文件") @Operation(summary = "上传初始化文件", description = "上传许可证、配置包等初始化依赖文件,返回 fileId 供后续任务引用。") public ApiResponse upload( @Parameter(description = "待上传的初始化文件") diff --git a/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java b/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java index 5744664..a8c7852 100644 --- a/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java +++ b/src/main/java/com/cisd/tms/modules/log/controller/AuditLogRestoreController.java @@ -4,7 +4,11 @@ package com.cisd.tms.modules.log.controller; import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.common.enums.ErrorCode; import com.cisd.tms.common.exception.BizException; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.modules.log.service.AuditLogRestoreService; +import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -27,6 +31,8 @@ public class AuditLogRestoreController { summary = "导入 ZIP 恢复审计日志", description = "上传审计日志的备份压缩包(.zip),系统将进行解密、验签并落库。" ) + @ReplayProtected + @AuditedOperation(module = ModuleCode.LOG, action = ActionType.RECOVER, summary = "恢复审计日志备份") public ApiResponse restoreFromZip(@RequestParam("file") MultipartFile file) { if (file.isEmpty() || !file.getOriginalFilename().endsWith(".zip")) { throw new BizException(ErrorCode.VALIDATE_FAILED.getCode(), "上传有效的 ZIP 格式备份文件"); @@ -36,4 +42,4 @@ public class AuditLogRestoreController { return ApiResponse.success(result); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cisd/tms/modules/log/controller/BackupConfigController.java b/src/main/java/com/cisd/tms/modules/log/controller/BackupConfigController.java index f9c35b0..345d3a1 100644 --- a/src/main/java/com/cisd/tms/modules/log/controller/BackupConfigController.java +++ b/src/main/java/com/cisd/tms/modules/log/controller/BackupConfigController.java @@ -4,9 +4,13 @@ import com.cisd.tms.common.api.ApiResponse; import com.cisd.tms.modules.auth.enums.AuthLevel; import com.cisd.tms.modules.auth.enums.RoleCode; import com.cisd.tms.modules.auth.security.RequireInternalAuth; +import com.cisd.tms.modules.log.annotation.AuditedOperation; import com.cisd.tms.modules.log.dto.BackupConfigRequest; import com.cisd.tms.modules.log.dto.BackupConfigResponse; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.modules.log.service.BackupConfigService; +import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -35,6 +39,8 @@ public class BackupConfigController { */ @Operation(summary = "保存或更新备份配置", description = "创建新的备份配置或覆盖更新已有的全局备份配置") @PostMapping + @ReplayProtected + @AuditedOperation(module = ModuleCode.LOG, action = ActionType.UPDATE, summary = "保存审计日志备份配置") @RequireInternalAuth(role = RoleCode.AUDIT_ADMIN, authLevel = AuthLevel.FULL) public ApiResponse saveOrUpdateConfig(@Validated @RequestBody BackupConfigRequest req) { backupConfigService.saveOrUpdateConfig(req); diff --git a/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java b/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java index 760b970..085d3f4 100644 --- a/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java +++ b/src/main/java/com/cisd/tms/modules/log/controller/BackupController.java @@ -13,6 +13,10 @@ import com.cisd.tms.modules.log.entity.BackupRecordEntity; import com.cisd.tms.modules.log.repository.BackupRecordRepository; import com.cisd.tms.modules.log.service.AuditBackupService; import com.cisd.tms.modules.log.service.BackupRecordService; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; +import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; @@ -37,6 +41,8 @@ public class BackupController { @PostMapping() @Operation(summary = "触发手动备份", description = "执行一次审计日志的备份任务,生成 ZIP 备份包") + @ReplayProtected + @AuditedOperation(module = ModuleCode.LOG, action = ActionType.BACKUP, summary = "触发审计日志备份") @RequireInternalAuth(role = RoleCode.AUDIT_ADMIN, authLevel = AuthLevel.FULL) public ApiResponse backup() { auditBackupService.manualBackup(); @@ -54,6 +60,7 @@ public class BackupController { @GetMapping("/record/download/{recordId}") @Operation(summary = "下载备份文件", description = "根据记录的唯一编码 (recordId) 下载对应的 ZIP 备份压缩包文件") + @AuditedOperation(module = ModuleCode.LOG, action = ActionType.EXPORT, summary = "下载审计日志备份文件") @RequireInternalAuth(role = RoleCode.AUDIT_ADMIN, authLevel = AuthLevel.FULL) public ResponseEntity downloadBackupFile(@PathVariable("recordId") String recordId) { @@ -72,4 +79,4 @@ public class BackupController { .contentLength(file.length()) .body(resource); } -} \ No newline at end of file +} diff --git a/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java b/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java index 13c1a2e..2b131ae 100644 --- a/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java +++ b/src/main/java/com/cisd/tms/modules/log/controller/OperationAuditController.java @@ -7,12 +7,16 @@ import com.cisd.tms.common.exception.BizException; import com.cisd.tms.modules.auth.enums.AuthLevel; import com.cisd.tms.modules.auth.enums.RoleCode; import com.cisd.tms.modules.auth.security.RequireInternalAuth; +import com.cisd.tms.modules.log.annotation.AuditedOperation; import com.cisd.tms.modules.log.dto.AuditLogReviewRequest; import com.cisd.tms.modules.log.dto.OperationAuditLogPageRequest; import com.cisd.tms.modules.log.dto.OperationAuditLogPageResponse; import com.cisd.tms.modules.log.dto.OperationAuditLogResponse; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.modules.log.service.OperationAuditService; import com.cisd.tms.security.internal.InternalApiAuthInterceptor; +import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; @@ -47,7 +51,9 @@ public class OperationAuditController { @Operation(summary = "审核审计日志", description = "审计管理员对特定的审计日志进行审核操作及意见批注") @PostMapping("/{logId}/review") + @ReplayProtected @RequireInternalAuth(role = RoleCode.AUDIT_ADMIN, authLevel = AuthLevel.FULL) + @AuditedOperation(module = ModuleCode.LOG, action = ActionType.UPDATE, summary = "审核审计日志") public ApiResponse reviewLog(@PathVariable String logId, @RequestBody AuditLogReviewRequest req) { String auditorUser = ""; // 默认 diff --git a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java index 2231373..525fbb7 100644 --- a/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java +++ b/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java @@ -6,6 +6,9 @@ import com.cisd.tms.common.exception.BizException; import com.cisd.tms.modules.auth.enums.AuthLevel; import com.cisd.tms.modules.auth.enums.RoleCode; import com.cisd.tms.modules.auth.security.RequireInternalAuth; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; import com.cisd.tms.modules.mk.dto.*; import com.cisd.tms.modules.mk.service.LmkService; import com.cisd.tms.modules.mk.service.MasterKeyActivateService; @@ -35,6 +38,7 @@ public class LmkController { @PostMapping("/masterKey/init") @Operation(summary = "初始化主密钥", description = "执行密码卡初始化识别、主密钥生成加载、内部密钥生成,并返回主密钥状态与校验值。") + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.CREATE, summary = "初始化主密钥") public ApiResponse initMasterKey() { return ApiResponse.success(lmkService.initMasterKey()); } @@ -43,6 +47,7 @@ public class LmkController { @Operation(summary = "销毁主密钥", description = "销毁当前密码卡中的主密钥,失败直接抛出异常。") @ReplayProtected @RequireInternalAuth(role = RoleCode.SUPER_ADMIN, authLevel = AuthLevel.FULL) + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.DELETE, summary = "销毁主密钥") public ApiResponse destroyMasterKey() { //销毁主密钥、内部密钥、用户密钥 lmkService.destroyMasterKey(); @@ -62,6 +67,7 @@ public class LmkController { */ @PostMapping("/masterKey/recover") @Operation(summary = "恢复全部密钥", description = "请求体需传入两份按 packetIndex 标识的备份包。两份备份包必须分别对应 packetIndex=1、2。服务端会依次恢复 LMK、IK 和用户密钥。") + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.RECOVER, summary = "恢复全部密钥") public ApiResponse recoverLmkEx(@Valid @RequestBody MasterKeyRecoverPacketsRequest request) { validatePacketIndexes(request.getPackets()); lmkService.recoverKeyPackets(request.getPackets()); @@ -71,6 +77,7 @@ public class LmkController { @PostMapping("/masterKey/activate") @Operation(summary = "设备激活") + @AuditedOperation(module = ModuleCode.DEVICE, action = ActionType.ENABLE, summary = "设备激活") public ApiResponse Activate(@RequestBody MasterKeyActivateRequest masterKeyActivateRequest) { masterKeyActivateService.activate(masterKeyActivateRequest); return ApiResponse.success(); diff --git a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java index 17c50c8..e3cb54b 100644 --- a/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java +++ b/src/main/java/com/cisd/tms/modules/openapi/service/controller/OpenApiController.java @@ -11,6 +11,10 @@ import com.cisd.tms.modules.openapi.service.dto.RawVerifyRequest; import com.cisd.tms.modules.openapi.service.dto.RawVerifyResponse; import com.cisd.tms.modules.openapi.service.dto.UploadCertRequest; import com.cisd.tms.modules.openapi.service.impl.OpenApiService; +import com.cisd.tms.modules.log.annotation.AuditedOperation; +import com.cisd.tms.modules.log.enums.ActionType; +import com.cisd.tms.modules.log.enums.ModuleCode; +import com.cisd.tms.security.internal.ReplayProtected; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -38,6 +42,8 @@ public class OpenApiController { @PostMapping("/rawSign") @Operation(summary = "裸签名", description = "基于 SM3 SM2 算法进行签名,返回 Base64 编码的签名值") + @ReplayProtected + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.EXECUTE, summary = "执行裸签名") public ApiResponse rawSign(@Valid @RequestBody RawSignRequest request) { log.info("收到裸签名请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); @@ -116,6 +122,8 @@ public class OpenApiController { @PostMapping("/dettachedSign") @Operation(summary = "分离签名", description = "基于 SM3 SM2 算法进行分离签名,返回 Base64 编码的签名值") + @ReplayProtected + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.EXECUTE, summary = "执行分离签名") public ApiResponse dettachedSign(@Valid @RequestBody DettachedSignRequest request) { log.info("收到分离签名请求, DN: {}, sessionId: {}", request.getDn(), request.getSessionId()); @@ -157,6 +165,8 @@ public class OpenApiController { @PostMapping("/uploadCert") @Operation(summary = "上传证书", description = "上传并导入证书") + @ReplayProtected + @AuditedOperation(module = ModuleCode.KEY, action = ActionType.IMPORT, summary = "上传并导入证书") public ApiResponse uploadCert(@Valid @RequestBody UploadCertRequest request) { log.info("收到上传证书请求, sessionId: {}", request.getSessionId()); diff --git a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java index e6134b1..be3d0d4 100644 --- a/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java +++ b/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java @@ -33,6 +33,7 @@ public class UpgradeController { @PostMapping("/upgrade-packages") @Operation(summary = "上传离线升级包", description = "上传离线升级包,返回 fileId 供升级预检和任务创建使用。") // @ReplayProtected + @AuditedOperation(module = ModuleCode.UPGRADE, action = ActionType.IMPORT, summary = "上传离线升级包") @RequireInternalAuth(role = RoleCode.OPS_ADMIN, authLevel = AuthLevel.FULL) public ApiResponse uploadPackage( @Parameter(description = "待上传的离线升级包") diff --git a/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java b/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java index 69f4156..681b73e 100644 --- a/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java +++ b/src/test/java/com/cisd/tms/security/internal/ReplayProtectedEndpointsTest.java @@ -4,7 +4,12 @@ import com.cisd.tms.modules.auth.controller.AuthAdminController; import com.cisd.tms.modules.auth.controller.AuthController; import com.cisd.tms.modules.device.controller.*; import com.cisd.tms.modules.init.controller.InitController; +import com.cisd.tms.modules.log.controller.AuditLogRestoreController; +import com.cisd.tms.modules.log.controller.BackupConfigController; +import com.cisd.tms.modules.log.controller.BackupController; +import com.cisd.tms.modules.log.controller.OperationAuditController; import com.cisd.tms.modules.mk.controller.LmkController; +import com.cisd.tms.modules.openapi.service.controller.OpenApiController; import com.cisd.tms.modules.upgrade.controller.UpgradeController; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -24,10 +29,10 @@ class ReplayProtectedEndpointsTest { @Test void shouldProtectSelectedUpgradeActions() throws Exception { - Assertions.assertNotNull(annotation(UpgradeController.class, "uploadPackage", org.springframework.web.multipart.MultipartFile.class)); - Assertions.assertNotNull(annotation(UpgradeController.class, "create", com.cisd.tms.modules.upgrade.dto.UpgradeCreateTaskRequest.class)); - Assertions.assertNotNull(annotation(UpgradeController.class, "execute", String.class)); - Assertions.assertNotNull(annotation(UpgradeController.class, "rollback", String.class)); + Assertions.assertNotNull(effectiveAnnotation(UpgradeController.class, "uploadPackage", org.springframework.web.multipart.MultipartFile.class)); + Assertions.assertNotNull(effectiveAnnotation(UpgradeController.class, "create", com.cisd.tms.modules.upgrade.dto.UpgradeCreateTaskRequest.class)); + Assertions.assertNotNull(effectiveAnnotation(UpgradeController.class, "execute", String.class)); + Assertions.assertNotNull(effectiveAnnotation(UpgradeController.class, "rollback", String.class)); } @Test @@ -43,8 +48,37 @@ class ReplayProtectedEndpointsTest { String.class, com.cisd.tms.modules.mk.dto.UKeySignDTO.class, jakarta.servlet.http.HttpServletRequest.class)); Assertions.assertNull(effectiveAnnotation(AuthAdminController.class, "issueSuperAdminUkeyBindingSign", com.cisd.tms.modules.mk.dto.UKeySignDTO.class)); - Assertions.assertNotNull(AnnotatedElementUtils.findMergedAnnotation(TimeConfigController.class, ReplayProtected.class)); - Assertions.assertNotNull(AnnotatedElementUtils.findMergedAnnotation(CryptoCardController.class, ReplayProtected.class)); + Assertions.assertNotNull(effectiveAnnotation(AuthController.class, "logout", jakarta.servlet.http.HttpServletRequest.class)); + Assertions.assertNull(AnnotatedElementUtils.findMergedAnnotation(TimeConfigController.class, ReplayProtected.class)); + Assertions.assertNull(AnnotatedElementUtils.findMergedAnnotation(CryptoCardController.class, ReplayProtected.class)); + } + + @Test + void shouldProtectAuditLogBackupAndRestoreStateChanges() throws Exception { + Assertions.assertNotNull(effectiveAnnotation(BackupController.class, "backup")); + Assertions.assertNotNull(effectiveAnnotation(BackupConfigController.class, "saveOrUpdateConfig", + com.cisd.tms.modules.log.dto.BackupConfigRequest.class)); + Assertions.assertNotNull(effectiveAnnotation(OperationAuditController.class, "reviewLog", + String.class, com.cisd.tms.modules.log.dto.AuditLogReviewRequest.class)); + Assertions.assertNotNull(effectiveAnnotation(AuditLogRestoreController.class, "restoreFromZip", + org.springframework.web.multipart.MultipartFile.class)); + } + + @Test + void shouldProtectOpenApiSigningAndCertificateMutationOnly() throws Exception { + Assertions.assertNotNull(effectiveAnnotation(OpenApiController.class, "rawSign", + com.cisd.tms.modules.openapi.service.dto.RawSignRequest.class)); + Assertions.assertNotNull(effectiveAnnotation(OpenApiController.class, "dettachedSign", + com.cisd.tms.modules.openapi.service.dto.DettachedSignRequest.class)); + Assertions.assertNotNull(effectiveAnnotation(OpenApiController.class, "uploadCert", + com.cisd.tms.modules.openapi.service.dto.UploadCertRequest.class)); + + Assertions.assertNull(effectiveAnnotation(OpenApiController.class, "rawVerify", + com.cisd.tms.modules.openapi.service.dto.RawVerifyRequest.class)); + Assertions.assertNull(effectiveAnnotation(OpenApiController.class, "dettachedVerify", + com.cisd.tms.modules.openapi.service.dto.DettachedVerifyRequest.class)); + Assertions.assertNull(effectiveAnnotation(OpenApiController.class, "dettachedVerifySimple", + com.cisd.tms.modules.openapi.service.dto.DettachedVerifyRequest.class)); } @Test @@ -59,14 +93,13 @@ class ReplayProtectedEndpointsTest { com.cisd.tms.modules.device.dto.network.IpWhitelistRequest.class)); Assertions.assertNotNull(effectiveAnnotation(IpWhitelistController.class, "deleteWhitelist", Long.class)); Assertions.assertNull(effectiveAnnotation(IpWhitelistController.class, "getWhitelistPage", - com.cisd.tms.modules.device.dto.network.IpWhitelistRequest.class)); + int.class, int.class, String.class)); - Assertions.assertNotNull(effectiveAnnotation(LmkController.class, "initMasterKey")); + Assertions.assertNull(effectiveAnnotation(LmkController.class, "initMasterKey")); Assertions.assertNotNull(effectiveAnnotation(LmkController.class, "destroyMasterKey")); - Assertions.assertNotNull(effectiveAnnotation(LmkController.class, "recoverLmkEx", + Assertions.assertNull(effectiveAnnotation(LmkController.class, "recoverLmkEx", com.cisd.tms.modules.mk.dto.MasterKeyRecoverPacketsRequest.class)); Assertions.assertNull(effectiveAnnotation(LmkController.class, "queryMasterKeyStatus")); - Assertions.assertNull(effectiveAnnotation(LmkController.class, "signIk", String.class)); } private static ReplayProtected annotation(Class type, String methodName, Class... parameterTypes) throws Exception {