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

462 lines
19 KiB
Markdown

# 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"
```