cisd/docs/plans/2026-04-09-replay-protection-plan.md
2026-04-09 15:14:49 +08:00

19 KiB

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

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

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

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

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

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

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

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

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

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

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"