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_noncetms_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_noncetms_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:
scopeprincipalIdnoncerequestTimestamprequestMethodrequestPathbodyHashremoteIpuserAgentexpiresAt
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_TOKENX-Request-TimestampX-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
LmkControllerwrite methods - selected
CryptoCardControllerwrite-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: 600cleanup-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"