--- name: callback_log 验签顺序 overview: 修订需求文档与 slot-pwa 实现:将「完整 callback_log 落库」从「收包第一时间」调整为「验签通过后」;验签失败仅保留应用日志,避免未授权请求刷写分库分表。 todos: - id: update-docs-02 content: 修订 02_provider_callback_log.md:流程图、§5 衔接、§6 验收、安全说明 status: completed - id: update-docs-parent content: 同步 game_gateway_risk_prd_v_2.md §4.3/§4.4 流程顺序 status: completed - id: reorder-middleware content: slot-pwa:POPAuth 在前,ProviderCallbackLog 在后;POPAuth 去掉 DB 验签回写 status: in_progress - id: insert-verify-passed content: insertReceived 验签通过态写入;清理 markVerifyPassed/Failed 在 POP 路径的调用 status: pending - id: tests-verify content: 更新/补充 callback_log 相关测试并跑 verify-slot-backend.sh status: pending isProject: false --- # callback_log 改为验签通过后落库 ## 问题判断 你的顾虑成立。当前设计与实现是: ```mermaid sequenceDiagram participant P as Provider participant L as ProviderCallbackLogMiddleware participant A as POPAuthMiddleware participant C as Controller P->>L: HTTP callback L->>L: INSERT callback_log verify_status=0 L->>A: next A->>A: verify signature alt verify fail A->>L: UPDATE verify_failed A-->>P: 401 JSON else verify pass A->>C: next end ``` - 需求:[02_provider_callback_log.md](docs/requirements/game_gateway/02_provider_callback_log.md) §3.1 / §5 要求「先 INSERT,再验签」 - 实现:[middleware.php](slot-pwa/config/middleware.php) 中 `ProviderCallbackLogMiddleware` 排在 `POPAuthMiddleware` **之前**;[ProviderCallbackLogMiddleware.php](slot-pwa/app/middleware/ProviderCallbackLogMiddleware.php) 注释也写明「须在验签之前」 - 验收 §6.2 要求「验签失败仍有 callback_log」——与防刷目标冲突 **原设计意图(合理但需边界)**:把每一次到达网关的请求都当作「证据」,包括验签失败,便于和 Provider 对账「对方声称发过回调」。代价是:公开 URL + 无 IP 白名单/限流时,攻击者可对 `user_transaction_*.game_provider_callback_log_`* 灌入大量 `MEDIUMTEXT`(`raw_body`/`raw_headers`)。 **你已确认的产品取舍**:验签通过后才 INSERT **完整** callback_log;验签失败不落库(仅应用日志;后续可选轻量 reject 表,本期不做)。 这与资金安全主链路一致:父文档 [game_gateway_risk_prd_v_2.md](docs/requirements/game_gateway_risk_prd_v_2.md) §5.1 将「验签 / IP 校验」的目的写明为「防止非法回调」——应先挡住非法流量,再落审计库。 --- ## 目标流程 ```mermaid sequenceDiagram participant P as Provider participant A as POPAuthMiddleware participant L as ProviderCallbackLogMiddleware participant C as Controller P->>A: HTTP callback A->>A: verify signature alt verify fail A->>A: structured error log only A-->>P: auth error JSON else verify pass A->>L: next L->>L: INSERT callback_log verify_status=1 L->>C: next end ``` 要点: - **验签失败**:`Log::error` 保留 `route`、`IP`、`body_hash`(可选)、`error`;**不**调用 `markVerifyFailed`(因无 `log_id`) - **验签通过**:`insertReceived` 时直接 `verify_status = VERIFY_PASSED(1)`,可删除事后 `markVerifyPassed` 的 UPDATE(减少一次写) - **解析失败**:仍在验签通过且已落库后,由 Logic 更新 `parse_status` / `process_status`(现有 `markParseFailed` 等不变) --- ## 文档修订(必须) ### 1. [02_provider_callback_log.md](docs/requirements/game_gateway/02_provider_callback_log.md) | 章节 | 修改 | | ------------- | ----------------------------------------------------------------------------------------------------------------------------- | | §1.2 In scope | 「第一时间落库」改为「**验签通过后**同步落库完整原始请求」 | | §3.1 流程图 | `Provider callback → 验签 → 同步写 callback_log(verify_status=1) → 解析 → …` | | §5 衔接表 | 「收到任意回调先 INSERT」改为「验签通过后 INSERT」;删除「验签完成再 UPDATE verify_status」作为主路径(保留解析/处理状态更新) | | §6 验收 | **改写 §6.2**:验签失败不落 callback_log,但应用日志可按 `request_id`/时间检索;**新增**:任意成功进入业务链路的 `provider_tx` 仍可 `callback_log_id` 追溯 `raw_body` | | 新增 §(安全说明) | 明确:防刷靠「验签门禁 +(建议)网关 IP 白名单/限流」;若未来需要攻击审计,用独立 reject 表或日志平台,不污染主 callback_log | ### 2. [game_gateway_risk_prd_v_2.md](docs/requirements/game_gateway_risk_prd_v_2.md) - §4.3 / §4.4 流程图中「同步写 callback_log」节点移到「验签」**之后**(与 02 子文档一致) --- ## 代码修订(slot-pwa) ### 1. 调整 Middleware 顺序 [config/middleware.php](slot-pwa/config/middleware.php) `pop` 组改为: ```php 'pop' => [ \app\middleware\POPAuthMiddleware::class, \app\middleware\ProviderCallbackLogMiddleware::class, ], ``` ### 2. [POPAuthMiddleware.php](slot-pwa/app/middleware/POPAuthMiddleware.php) - 验签失败:仅 `Log::error` + 返回 JSON;**移除** `markVerifyFailed`(依赖已落库的 `log_id`) - 验签成功:**移除** `markVerifyPassed`;直接 `$handler($request)` 进入下一 Middleware 落库 - 平台配置缺失:同样不落库,只打日志返回错误 ### 3. [ProviderCallbackLogMiddleware.php](slot-pwa/app/middleware/ProviderCallbackLogMiddleware.php) - 更新类注释:在验签 Middleware **之后**执行,仅对验签通过的请求落库 - `recordReceivedFromRequest` 失败时:当前 catch 后仍 `return $handler`——建议改为 **记录错误并返回 5xx**(避免无 callback_log 却继续扣款);与产品确认可放在实现阶段一并收紧 ### 4. [ProviderCallbackLogService.php](slot-pwa/app/service/game/ProviderCallbackLogService.php) + [GameProviderCallbackLogModel.php](slot-pwa/app/model/shard/GameProviderCallbackLogModel.php) - `insertReceived` 增加参数或拆方法:`insertReceivedAfterVerifyPassed()`,写入时 `verify_status = VERIFY_PASSED` - `markVerifyPassed` / `markVerifyFailed`:保留方法但标注「仅兼容旧路径」或删除未引用调用(POP 路径不再使用 failed 回写) - `recordReceivedFromRequest` 仅在验签后 Middleware 调用,语义与命名可在 PHPDoc 中写清 ### 5. 测试 - [GameProviderCallbackLogShardModelTest.php](slot-pwa/tests/Feature/GameProviderCallbackLogShardModelTest.php) / [ProviderCallbackLogTestCase.php](slot-pwa/tests/Support/ProviderCallbackLogTestCase.php):若直接测 `insertReceived`,默认 `VERIFY_PENDING` 仍可用于 Model 单测;新增或调整用例覆盖「验签后插入为 PASSED」 - 可选:Middleware 顺序集成测试(验签失败不触发 insert)——若项目已有 HTTP 测试基建则补一条 --- ## 不在本期但必须知晓的风险 | 风险 | 缓解 | | ------------------------------- | --------------------------------------------------------------------------------- | | 验签失败无 DB 记录,与 Provider 扯皮时只能靠日志 | 日志字段标准化:`provider_code`、`source_ip`、`body_hash`、`x-request-id`;日志保留期与 Provider 对齐 | | CPU 层仍可被刷验签 | 网关层 Provider IP 白名单 + rate limit(nginx/云 WAF);本期文档 § 安全说明中写为 **建议项** | | `uid=0` 分片仍可能被合法但解析失败的请求写入 | 仅发生在验签通过后,可接受;与刷库攻击不同类 | --- ## 实施顺序建议 1. 先改 02 子文档 + 父文档流程图(避免实现与需求再漂移) 2. 调整 middleware 顺序与 POP / CallbackLog 职责 3. Model/Service 插入时带 `VERIFY_PASSED` 4. `按php-clean-code.mdc 文件验证代码规范` 5. 跑 `verify-slot-backend.sh` + 相关 PHPUnit