fix(auth): 微信OAuth服务工厂改为真单例,修复回调state必校验失败 (#1718) #1719

Merged
auto-approve-bot merged 1 commits from fix/wechat-oauth-service-singleton-1718 into develop 2026-09-05 17:17:08 +08:00
Owner

背景

Issue #1718 配置微信登录环境变量后做端到端验证时发现:微信登录在真实凭证下完全不可用

/api/v1/auth/wechat/url 生成的 state 存在 MemoryStateStore 里,但 get_wechat_oauth_service() 名义上是单例工厂、实际每次调用都 return WechatOAuthService() —— 每个请求拿到全新实例和全新 state store。微信回调 /wechat/callback 校验 state 时永远找不到,必返回 400「无效的 state 参数,请求可能已过期或被篡改」。

此 bug 在 mock 模式下被掩盖(未配置时走 mock 分支,state 校验同样会失败,但前端联调可能直接用 mock 链路未暴露)。

修改

  • packages/application/auth/wechat_oauth_service.py:模块级 _oauth_service_singleton 懒加载单例,state store 跨请求共享;注释标明多实例部署需替换为 Redis store
  • tests/unit/test_wechat_oauth_service.py:新增 2 个回归测试
    • 工厂两次调用返回同一实例
    • state 跨工厂调用可校验通过、一次性消费后重放失败(mock 微信 API)

验证

  • 107 个微信/auth 相关单测全过;black/isort/ruff 全绿
  • staging/生产 API 容器已配置 WECHAT_OPEN_APP_ID/SECRET/REDIRECT_URI(本次代码修复的前置部署工作)
  • 凭证已向微信侧验证有效(假 code 探测返回 errcode 40029 invalid code,非 40125/40013 凭证错误)

注意

单容器单 uvicorn worker 下此修复即可;未来 API 多 worker 或多副本时,MemoryStateStore 需换成 Redis 实现(代码里已有 TODO)。

## 背景 Issue #1718 配置微信登录环境变量后做端到端验证时发现:**微信登录在真实凭证下完全不可用**。 `/api/v1/auth/wechat/url` 生成的 state 存在 `MemoryStateStore` 里,但 `get_wechat_oauth_service()` 名义上是单例工厂、实际每次调用都 `return WechatOAuthService()` —— 每个请求拿到全新实例和全新 state store。微信回调 `/wechat/callback` 校验 state 时永远找不到,必返回 400「无效的 state 参数,请求可能已过期或被篡改」。 此 bug 在 mock 模式下被掩盖(未配置时走 mock 分支,state 校验同样会失败,但前端联调可能直接用 mock 链路未暴露)。 ## 修改 - `packages/application/auth/wechat_oauth_service.py`:模块级 `_oauth_service_singleton` 懒加载单例,state store 跨请求共享;注释标明多实例部署需替换为 Redis store - `tests/unit/test_wechat_oauth_service.py`:新增 2 个回归测试 - 工厂两次调用返回同一实例 - state 跨工厂调用可校验通过、一次性消费后重放失败(mock 微信 API) ## 验证 - 107 个微信/auth 相关单测全过;black/isort/ruff 全绿 - staging/生产 API 容器已配置 WECHAT_OPEN_APP_ID/SECRET/REDIRECT_URI(本次代码修复的前置部署工作) - 凭证已向微信侧验证有效(假 code 探测返回 errcode 40029 invalid code,非 40125/40013 凭证错误) ## 注意 单容器单 uvicorn worker 下此修复即可;未来 API 多 worker 或多副本时,MemoryStateStore 需换成 Redis 实现(代码里已有 TODO)。
xiaoxia added 1 commit 2026-09-05 17:11:59 +08:00
fix(auth): 微信OAuth服务工厂改为真单例,修复回调state必校验失败(#1718)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 1m22s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 1m32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m46s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m47s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m12s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m58s
AI Code Review / AI Code Review (pull_request) Successful in 3m2s
CI/CD Pipeline / Validate - Security (pull_request) Successful in 4m28s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 2m6s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 15s
98f0619b46
get_wechat_oauth_service() 注释写单例但每次 new WechatOAuthService(),
/wechat/url 生成的 state 与 /wechat/callback 校验落在不同 MemoryStateStore,
真实环境配置后回调必现 400「无效的 state 参数」,微信登录完全不可用。

- 模块级 _oauth_service_singleton 懒加载,state store 跨请求共享
- 新增2个回归测试:单例同一性 + state 跨工厂调用可校验/一次性消费

🚀 预览环境已部署

项目 详情
PR号 #1719
预览链接 https://pr-1719.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1719 | | 预览链接 | [https://pr-1719.preview.xiaoxiajianji.com](https://pr-1719.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:否
  • 阻塞级问题数量:0 个

📊 审查概览

  • 整体评价:通过
  • 建议级问题数量:1 个

🔴 阻塞级问题(必须修复)

💡 改进建议(不阻塞合并)

  1. [packages/application/auth/wechat_oauth_service.py: 208] 线程安全隐患
    • 具体内容:当前的单例实现 if _oauth_service_singleton is None: ... 在多线程环境下(如 gunicorn 多线程模式)存在竞态条件。虽然 Python 的 GIL 提供了一定程度的保护,但在 WechatOAuthService() 初始化过程中发生线程切换仍可能导致创建多个实例,从而引入 state 不共享的回归风险。
    • 修改建议:建议使用线程锁(如 import threading; _lock = threading.Lock())来确保实例化的原子性,或者使用 functools.lru_cache 装饰器来实现更简洁的线程安全单例。

良好实践

  1. 回归测试覆盖充分:新增的测试用例 test_state_survives_across_factory_calls 非常出色,不仅验证了单例模式,还模拟了真实的业务场景(生成 URL -> 回调校验 -> 重放攻击防御),有效确保了修复的完整性。
  2. 注释清晰:代码中关于“模块级单例”和“多实例部署”的注释准确说明了设计意图和局限性,有助于后续维护。
  3. 测试隔离性好:测试中正确使用了 monkeypatch 重置全局单例状态,避免了测试用例之间的相互干扰。

格式检查通过 | 逻辑审查通过 | 性能良好


🤖 由 AI 代码审查机器人自动生成 | 2026-09-05 09:15:01 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:否 - 阻塞级问题数量:0 个 ### 📊 审查概览 - 整体评价:通过 - 建议级问题数量:1 个 ### 🔴 阻塞级问题(必须修复) 无 ### 💡 改进建议(不阻塞合并) 1. **[packages/application/auth/wechat_oauth_service.py: 208] 线程安全隐患** - 具体内容:当前的单例实现 `if _oauth_service_singleton is None: ...` 在多线程环境下(如 gunicorn 多线程模式)存在竞态条件。虽然 Python 的 GIL 提供了一定程度的保护,但在 `WechatOAuthService()` 初始化过程中发生线程切换仍可能导致创建多个实例,从而引入 state 不共享的回归风险。 - 修改建议:建议使用线程锁(如 `import threading; _lock = threading.Lock()`)来确保实例化的原子性,或者使用 `functools.lru_cache` 装饰器来实现更简洁的线程安全单例。 ### ✅ 良好实践 1. **回归测试覆盖充分**:新增的测试用例 `test_state_survives_across_factory_calls` 非常出色,不仅验证了单例模式,还模拟了真实的业务场景(生成 URL -> 回调校验 -> 重放攻击防御),有效确保了修复的完整性。 2. **注释清晰**:代码中关于“模块级单例”和“多实例部署”的注释准确说明了设计意图和局限性,有助于后续维护。 3. **测试隔离性好**:测试中正确使用了 `monkeypatch` 重置全局单例状态,避免了测试用例之间的相互干扰。 --- ✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能良好 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-09-05 09:15:01 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
auto-approve-bot merged commit 8a6d51f6c3 into develop 2026-09-05 17:17:08 +08:00
auto-approve-bot deleted branch fix/wechat-oauth-service-singleton-1718 2026-09-05 17:17:08 +08:00

🗑️ 预览环境已清理

PR #1719 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1719 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.