# Replay Protection Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Add TiDB-backed replay protection for `/openapi/**` and selected sensitive internal `/api/**` write/execute endpoints. **Architecture:** Replace the current in-memory OpenAPI nonce guard with a database-backed replay nonce service, then add an internal replay interceptor that only protects annotated high-risk handlers. Persist nonce claims in TiDB with a unique key, record replay-related security events, and reject protected requests when replay validation cannot be completed. **Tech Stack:** Spring Boot 3, MyBatis-Plus, Flyway SQL migration, JUnit 5, Mockito, MockMvc. --- ### Task 1: Add Replay Nonce and Security Event Schema **Files:** - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V5__add_replay_protection_tables.sql` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/db/migration/V1__tms_schema_full.sql` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/TmsApplicationTests.java` **Step 1: Write the failing schema assertions** Add assertions that `V1__tms_schema_full.sql` or the new migration defines: - `tms_replay_nonce` - `tms_security_event` - unique key on `(scope, principal_id, nonce)` - index on `expires_at` **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=TmsApplicationTests test` Expected: FAIL because replay protection tables do not exist in schema artifacts yet. **Step 3: Write minimal implementation** Create `V5__add_replay_protection_tables.sql` with: - `tms_replay_nonce` - `tms_security_event` Update `V1__tms_schema_full.sql` so fresh-init environments also include the same final-state tables. **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=TmsApplicationTests test` Expected: PASS for schema assertions. **Step 5: Commit** ```bash git add src/main/resources/db/migration/V1__tms_schema_full.sql src/main/resources/db/migration/V5__add_replay_protection_tables.sql src/test/java/com/cisd/tms/TmsApplicationTests.java git commit -m "feat: add replay protection schema" ``` ### Task 2: Add Replay Persistence Models and Repositories **Files:** - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/entity/ReplayNonceEntity.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/entity/SafetyEventEntity.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/mapper/ReplayNonceMapper.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/mapper/SecurityEventMapper.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/repository/ReplayNonceRepository.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/repository/SecurityEventRepository.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/repository/impl/ReplayNonceRepositoryImpl.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/repository/impl/SecurityEventRepositoryImpl.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceRepositoryTest.java` **Step 1: Write the failing repository test** Add tests for: - saving a nonce record - detecting duplicate `(scope, principalId, nonce)` - saving a security event **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=ReplayNonceRepositoryTest test` Expected: FAIL because replay persistence classes do not exist. **Step 3: Write minimal implementation** Create entities, mappers, repositories, and repository impls consistent with the existing `modules/*/repository/impl` pattern. Represent: - `scope` - `principalId` - `nonce` - `requestTimestamp` - `requestMethod` - `requestPath` - `bodyHash` - `remoteIp` - `userAgent` - `expiresAt` **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=ReplayNonceRepositoryTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/modules/security/replay src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceRepositoryTest.java git commit -m "feat: add replay persistence layer" ``` ### Task 3: Add Replay Protection Service and Domain Types **Files:** - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/service/ReplayNonceService.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/service/impl/ReplayNonceServiceImpl.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/dto/ReplayCheckRequest.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/dto/ReplayCheckResult.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/enums/ReplayScope.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/enums/SafetyEventType.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/exception/ReplayAttackException.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceServiceTest.java` **Step 1: Write the failing service test** Add tests for: - first nonce claim succeeds - duplicate nonce claim returns replay result - duplicate nonce with different path/body hash marks mismatch - expired timestamp is rejected before claim - repository failure causes protected request to fail closed **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=ReplayNonceServiceTest test` Expected: FAIL because replay service does not exist. **Step 3: Write minimal implementation** Implement a service that: - validates timestamp skew - attempts nonce claim via repository - on duplicate, compares request fingerprint - writes security events - throws a dedicated replay exception for interceptor handling Keep timestamp comparison logic in one place so both OpenAPI and internal interceptors share the same rule. **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=ReplayNonceServiceTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/modules/security/replay src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceServiceTest.java git commit -m "feat: add replay protection service" ``` ### Task 4: Replace OpenAPI In-Memory Replay Guard **Files:** - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptor.java` - Delete: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/security/openapi/OpenApiReplayGuard.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java` **Step 1: Write the failing interceptor tests** Add tests for: - valid request passes and claims nonce - duplicate nonce is rejected - timestamp expired is rejected - invalid signature is rejected before nonce claim - duplicate nonce with same appId but different body hash is treated as replay mismatch **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=OpenApiSignAuthInterceptorTest test` Expected: FAIL because interceptor still uses `OpenApiReplayGuard`. **Step 3: Write minimal implementation** Refactor `OpenApiSignAuthInterceptor` to: - compute `bodyHash` - validate timestamp - validate signature - call `ReplayNonceService` Remove `OpenApiReplayGuard`. Do not change the existing request header names in this task. **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=OpenApiSignAuthInterceptorTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/security/openapi src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java git commit -m "feat: persist openapi replay protection" ``` ### Task 5: Add Internal Replay Annotation and Interceptor **Files:** - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/annotation/ReplayProtected.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/security/internal/InternalReplayProtectionInterceptor.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/common/config/WebMvcConfig.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/security/internal/InternalReplayProtectionInterceptorTest.java` **Step 1: Write the failing interceptor tests** Add tests for: - unannotated internal handler is ignored - annotated handler requires `X-Request-Timestamp` - annotated handler requires `X-Request-Nonce` - annotated handler claims nonce with current session token - replayed annotated request is rejected **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=InternalReplayProtectionInterceptorTest test` Expected: FAIL because annotation and interceptor do not exist. **Step 3: Write minimal implementation** Create: - `@ReplayProtected` - internal interceptor that runs after session auth - registration in `WebMvcConfig` The interceptor should use: - `InternalApiAuthInterceptor.ATTR_SESSION_TOKEN` - `X-Request-Timestamp` - `X-Request-Nonce` Reject protected requests when session token is missing or replay validation fails. **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=InternalReplayProtectionInterceptorTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/modules/security/replay/annotation/ReplayProtected.java src/main/java/com/cisd/tms/security/internal/InternalReplayProtectionInterceptor.java src/main/java/com/cisd/tms/common/config/WebMvcConfig.java src/test/java/com/cisd/tms/security/internal/InternalReplayProtectionInterceptorTest.java git commit -m "feat: add internal replay interceptor" ``` ### Task 6: Annotate First-Batch Sensitive Internal Endpoints **Files:** - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/init/controller/InitController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java` **Step 1: Write the failing controller coverage tests** Add tests for: - protected endpoints reject missing replay headers - login/captcha endpoints remain unprotected - a representative protected endpoint accepts replay headers when replay service allows it **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=UpgradeControllerTest,AuthControllerTest test` Expected: FAIL because first-batch endpoints are not annotated. **Step 3: Write minimal implementation** Annotate only: - init/create/execute/reset-create/reset-execute - upgrade/create/execute/rollback - logout/change-password - all auth admin POST methods - time/network/whitelist/device restart writes - all `LmkController` write methods - selected `CryptoCardController` write-state methods Do not annotate: - login - captcha - random issuance - GET endpoints - pure preview endpoints **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=UpgradeControllerTest,AuthControllerTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/modules/init/controller/InitController.java src/main/java/com/cisd/tms/modules/upgrade/controller/UpgradeController.java src/main/java/com/cisd/tms/modules/auth/controller/AuthController.java src/main/java/com/cisd/tms/modules/auth/controller/AuthAdminController.java src/main/java/com/cisd/tms/modules/device/controller/TimeConfigController.java src/main/java/com/cisd/tms/modules/device/controller/NetworkConfigController.java src/main/java/com/cisd/tms/modules/device/controller/IpWhitelistController.java src/main/java/com/cisd/tms/modules/device/controller/DeviceController.java src/main/java/com/cisd/tms/modules/mk/controller/LmkController.java src/main/java/com/cisd/tms/modules/device/controller/CryptoCardController.java src/test/java/com/cisd/tms/modules/upgrade/controller/UpgradeControllerTest.java src/test/java/com/cisd/tms/modules/auth/controller/AuthControllerTest.java git commit -m "feat: protect sensitive internal endpoints from replay" ``` ### Task 7: Map Replay Exceptions and Add Structured Responses **Files:** - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/common/enums/ErrorCode.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java` **Step 1: Write the failing exception handler test** Add tests for: - replay attack exception returns expected code/message - timestamp expired exception returns expected code/message **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=GlobalExceptionHandlerTest test` Expected: FAIL because replay exceptions are not mapped yet. **Step 3: Write minimal implementation** Add replay-related error codes and map: - replay detected - request timestamp expired - replay guard unavailable Keep response shape consistent with existing `ApiResponse.fail(...).withPath(...)`. **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=GlobalExceptionHandlerTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/common/exception/GlobalExceptionHandler.java src/main/java/com/cisd/tms/common/enums/ErrorCode.java src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java git commit -m "feat: expose replay protection errors" ``` ### Task 8: Add Replay Nonce Cleanup Job **Files:** - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/config/ReplayProtectionProperties.java` - Add: `/Users/waner/Work/CISD/文档/tms-framework/src/main/java/com/cisd/tms/modules/security/replay/job/ReplayNonceCleanupJob.java` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/src/main/resources/application.yml` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceCleanupJobTest.java` **Step 1: Write the failing cleanup job test** Add tests for: - deleting expired rows in small batches - leaving non-expired rows untouched **Step 2: Run test to verify it fails** Run: `mvn -q -Dtest=ReplayNonceCleanupJobTest test` Expected: FAIL because cleanup job does not exist. **Step 3: Write minimal implementation** Add: - replay protection properties - scheduled cleanup job - config defaults for ttl and delete batch size Use modest defaults such as: - `ttl-seconds: 600` - `cleanup-batch-size: 1000` **Step 4: Run test to verify it passes** Run: `mvn -q -Dtest=ReplayNonceCleanupJobTest test` Expected: PASS **Step 5: Commit** ```bash git add src/main/java/com/cisd/tms/modules/security/replay/config src/main/java/com/cisd/tms/modules/security/replay/job src/main/resources/application.yml src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceCleanupJobTest.java git commit -m "feat: add replay nonce cleanup job" ``` ### Task 9: Run Focused Verification **Files:** - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceRepositoryTest.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/modules/security/replay/ReplayNonceServiceTest.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/security/openapi/OpenApiSignAuthInterceptorTest.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/security/internal/InternalReplayProtectionInterceptorTest.java` - Test: `/Users/waner/Work/CISD/文档/tms-framework/src/test/java/com/cisd/tms/common/exception/GlobalExceptionHandlerTest.java` **Step 1: Run focused replay tests** Run: `mvn -q -Dtest=ReplayNonceRepositoryTest,ReplayNonceServiceTest,OpenApiSignAuthInterceptorTest,InternalReplayProtectionInterceptorTest,GlobalExceptionHandlerTest test` Expected: PASS **Step 2: Run representative controller tests** Run: `mvn -q -Dtest=UpgradeControllerTest,AuthControllerTest test` Expected: PASS **Step 3: Run compile check** Run: `mvn -q -DskipTests compile` Expected: PASS **Step 4: Commit** ```bash git add -A git commit -m "test: verify replay protection implementation" ``` ### Task 10: Update Docs and Integration Guidance **Files:** - Modify: `/Users/waner/Work/CISD/文档/tms-framework/README.md` - Modify: `/Users/waner/Work/CISD/文档/tms-framework/docs/openapi/auth-mk-frontend-integration.md` - Add: `/Users/waner/Work/CISD/文档/tms-framework/docs/plans/2026-04-09-replay-protection-design.md` - Add: `/Users/waner/Work/CISD/文档/tms-framework/docs/plans/2026-04-09-replay-protection-plan.md` **Step 1: Document OpenAPI replay headers** Update README and integration docs to describe: - OpenAPI replay protection flow - internal sensitive API replay headers - expected replay failure behavior **Step 2: Verify docs are consistent with actual endpoint scope** Review the protected endpoint list against controller annotations. **Step 3: Commit** ```bash git add README.md docs/openapi/auth-mk-frontend-integration.md docs/plans/2026-04-09-replay-protection-design.md docs/plans/2026-04-09-replay-protection-plan.md git commit -m "docs: add replay protection guidance" ```