- Sprint 执行计划 v1.1:§1.1 补第一批交付实据;T9 SPIKE 结论回写 Sprint 1 表 (T-P0-16 通过 2.8ms;T-P0-12 有条件;T-P0-10 原型 6/6 + 真实照片重验前置); Sprint 2 表标注 T-P2-01~06 交付;新增 §9 两批交付记录 - 开发任务拆解 v1.5:交付回写 + 新风险登记(RTMPose 域差异、BiRefNet 异步化) - 新增 开发任务包/:三批多 agent 并行的协作规程与 14 份任务契约 - 新增 前置开通清单-云资源与资质.md - 清理已用毕的评审报告(文档评审 2026-08-13、项目整体评审 2026-08-14), README 中相应引用改为留痕表述
5.7 KiB
T10 · 登录·华为账号(代码先行,联调后置)
对应任务:
T-P1-12| 需求:FR-ACC-01(登录成功率 ≥99%、UnionID 映射唯一、首次自动建用户)| 分支:task/t10-auth-huawei| worktree:/home/xsl/meida_backend-t10先读《README.md(协作规程)》,遵守全部纪律。
1. 目标
实现 POST /auth/huawei:客户端传华为 Account Kit 签发的 id_token,服务端按 OIDC 标准做 JWKS 验签 + claims 校验,映射到内部用户并签发双令牌(access + refresh,复用 T4 已交付的 issue_session)。
本任务性质是"代码先行":华为 AGC 后台尚未配置(见《前置开通清单》§3),因此真实联调后置;全部逻辑用自造 RSA 密钥对 + 本地 JWKS 做单测/集成,凭证到位后只改 .env 即可上线。
2. 已就绪的契约(协调提交已提供)
配置(config.py 已加)
huawei_jwks_url: str = "https://oauth-login.cloud.huawei.com/oauth2/v3/certs" # HUAWEI_JWKS_URL
huawei_audience: str = "" # HUAWEI_AUDIENCE,= AGC App ID,联调时填
huawei_issuer: str = "https://accounts.huawei.com" # HUAWEI_ISSUER(以 Account Kit 文档为准,可配)
huawei_jwks_cache_seconds: int = 86400
可复用的现有设施(先读再写)
packages/identity/service.py:get_or_create_by_external_identity(provider, union_id, open_id, register_source)(T4 已有);issue_session(user_id) -> SessionIssued(T4 交付,含 refresh 轮换结构)。packages/shared/security.py:create_access_token(...)(HS256 access)。packages/identity/schemas.py:TokenPairResponse(T4 交付)——直接复用,不要在本任务重复定义。pyjwt[crypto]已装(含 cryptography,RS256 验签可用)。
端点归属
apps/api/routers/auth.py 归你(T4 已合并完,本批无他人碰它)。apps/api/routers/auth_sms.py 归 T11,禁碰。
3. 实现要点
允许改动:packages/identity/huawei.py(新建,核心逻辑全在此)、apps/api/routers/auth.py(追加端点)、测试文件。不改 identity/service.py 与 identity/schemas.py(避免与 T11 冲突——你的模型定义在 huawei.py 内)。
packages/identity/huawei.py:HuaweiIdTokenVerifier:构造参数(jwks_url, audience, issuer, cache_seconds, *, http_fetch=None, now=None)——http_fetch是Callable[[str], Awaitable[str]](单测注入本地 JWKS,不连外网);JWKS 内存缓存(TTL 按配置,kid 变化时强制刷新一次并重试验签);verify(id_token) -> HuaweiClaims(frozen dataclass:union_id/open_id/exp/iat):RS256 验签(jwt.decode+PyJWK/手工 kid 匹配)、aud(等于配置 audience,空配置时拒绝而不是跳过——联调前后都必须严格)、iss、exp/iat、union_id缺失拒绝(华为子账号场景无 union_id 时降级用 open_id 的策略不做,注释说明留给联调期决定);- 错误映射:验签失败/claims 不符 →
AppError(TOKEN_INVALID)(40102,已有);JWKS 拉取失败 →AppError(UPSTREAM_ERROR)(60000,已有)——不需要新错误码。
- 路由(auth.py 追加):
@router.post("/huawei", summary="华为账号授权登录(FR-ACC-01)") async def huawei_login(payload: HuaweiLoginRequest, ...) -> ...: claims = await verifier.verify(payload.id_token) user = await identity.get_or_create_by_external_identity( provider="huawei", union_id=claims.union_id, open_id=claims.open_id, register_source="huawei") issued = await identity.issue_session(user.user_id) access = create_access_token(...) return ok(TokenPairResponse(...)) # 复用 T4 模型- verifier 用依赖注入(路由模块内建一个
get_huawei_verifier的 FastAPI Depends,从 settings 构造并缓存到app.state或模块级 lru——不改 deps.py,在 auth.py 内实现); HuaweiLoginRequest(id_token: str)定义在 huawei.py。
- verifier 用依赖注入(路由模块内建一个
- 模块 docstring 写明:真实联调前置 = AGC 应用创建 + 证书指纹双套 +
HUAWEI_AUDIENCE填 App ID(引用《前置开通清单》§3)。
4. 测试要求
- 单测
tests/unit/test_huawei_verifier.py:测试内用cryptography生成 RSA 密钥对、自签 id_token + 本地 JWKS dict:- 合法 token → claims 解析正确;
- 错误签名(换密钥)→ 40102;过期 → 40101(复用 TOKEN_EXPIRED);aud 不符 → 40102;audience 未配置 → 40102;
- JWKS 拉取失败 → 60000;kid 轮换 → 缓存刷新后验签成功。
- 集成
tests/integration/test_auth_huawei.py(新文件):monkeypatch 注入 FakeFetcher(本地 JWKS)→POST /v1/auth/huawei→ 200 双令牌;再次登录同 union_id → 同一 user_id(映射唯一);新 access 可调GET /v1/quota。
5. 禁改清单
通用禁改之外:packages/identity/{service,repo,schemas,tokens}.py、apps/api/routers/auth_sms.py、packages/{kb,rules,retrieval}/**。
6. 验收标准(FR-ACC-01)
- 验签逻辑完整(签名/aud/iss/exp/iat 全查),audience 未配置时拒绝启动校验路径;
- UnionID→内部 user_id 映射唯一,首次登录自动建用户(集成断言同 ID 复登);
- 全链路不连外网(FakeFetcher 注入);
- 全部检查绿(mypy 范围同 T6 列表)。
7. 提交信息
feat(identity): Huawei Account Kit login via OIDC JWKS verification
POST /auth/huawei:JWKS 验签(kid 缓存/轮换)+ claims 严格校验 +
UnionID 映射 + T4 双令牌签发(FR-ACC-01;真实联调待 AGC 配置)。
偏差说明:<如有写在这里,无则写"无">