duoqi-api/docs/fusion-auth-integration.md
Wang Zhuoxuan a2282975ca feat: 集成阿里云融合认证实现手机号一键登录与登录方式管理
- 新增 POST /auth/fusion/token 获取 SDK 鉴权 Token
- 新增 POST /auth/fusion/verify 用 verifyToken 换取手机号并登录/注册
- 新增 GET /auth/providers 按平台返回可用登录方式列表
- 新增 PUT /admin/auth-providers 管理端热切换第三方登录开关
- 新增 appSettings 表存储运行时配置,支持不重启生效
- 修复 schema 中超长外键名称导致的 db:push 失败
2026-05-27 22:50:11 +08:00

16 KiB
Raw Blame History

阿里云融合认证集成 — 设计与实施计划

Phase 1e: Phone Number Authentication Created: 2026-05-27 Status: Implemented

概述

集成阿里云号码认证服务的融合认证方案,为多奇平台提供手机号一键登录能力。融合认证是一种校验用户手机号的云原生服务,集成了号码认证(运营商网关取号)、短信验证码、语音验证码等通信功能。

目标

  • 新增 POST /auth/fusion/token 端点,为客户端 SDK 提供鉴权 Token对齐 Flutter 客户端 FUSION_AUTH_INTEGRATION.md 第 5.1 节)
  • 新增 POST /auth/fusion/verify 端点,用运营商 verifyToken 换取手机号并完成登录(对齐 Flutter 客户端第 5.2 节)
  • 支持游客账号通过手机号关联升级为正式账号

不在范围内

  • 短信验证码/语音验证码登录(号码认证降级到短信时由阿里云模板自动处理,服务端感知不变)
  • Flutter 客户端 SDK 集成(客户端侧独立进行,见 duoqi-flutter/docs/FUSION_AUTH_INTEGRATION.md
  • 修改现有 POST /auth/phone(保留 501 占位,未来可用于非融合认证的手机登录)

架构分析

现有认证架构

项目已实现 guesthuaweiapple 三种登录方式,采用统一的 findOrCreate* 模式:

客户端 ──▶ 路由层(Zod校验) ──▶ 服务层(业务逻辑) ──▶ 数据库(users表)
                                    │
                              signTokens() + buildLoginResponse()

关键设计:

  • users 表以 (authType, authId) 唯一索引标识用户
  • authType 枚举已包含 'phone'authId 用于存储手机号
  • JWT payload 携带 { userId, authType, tier }
  • 登录响应统一为 { user, tokens } 结构

融合认证集成点

融合认证引入一个两步交互流程,比现有的华为/Apple 认证多一个"预取 Token"步骤:

┌──────────┐       ┌──────────────┐       ┌──────────────────┐
│  客户端   │       │ duoqi-api    │       │ 阿里云 Dypnsapi  │
└────┬─────┘       └──────┬───────┘       └────────┬─────────┘
     │                    │                         │
     │ ① POST /auth/fusion/token                   │
     │  { platform, packageName?, bundleId? }       │
     │───────────────────▶│                         │
     │                    │ ② GetFusionAuthToken    │
     │                    │────────────────────────▶│
     │                    │◀── { authToken } ────────│
     │◀── { fusionAuthToken } ──│                    │
     │                    │                         │
     │  [客户端 SDK 使用 authToken 完成认证]          │
     │  [获得 verifyToken] │                         │
     │                    │                         │
     │ ③ POST /auth/fusion/verify                   │
     │  { verifyToken }   │                         │
     │───────────────────▶│                         │
     │                    │ ④ VerifyWithFusionAuthToken
     │                    │────────────────────────▶│
     │                    │◀── { phoneNumber, verifyResult, phoneScore }
     │                    │                         │
     │                    │ ⑤ 查找或创建用户,签发JWT  │
     │◀── { user, tokens } │                        │
     │                    │                         │

路由路径对齐 Flutter 客户端 FUSION_AUTH_INTEGRATION.md 第 5 节的定义。


关键设计决策

决策 1手机号存储格式

选择:存储阿里云返回的原始手机号字符串(如 18012341234

理由:

  • VerifyWithFusionAuthToken 在号码认证通过时返回完整手机号(非掩码)
  • 完整手机号作为 authId 可确保用户唯一性
  • 掩码号(如 180****1234)仅出现在日志或审计场景,不用于身份标识

替代方案(未采纳):

  • 存储掩码号:无法唯一标识用户,不可行
  • 对手机号做哈希:丧失可读性,且哈希碰撞风险虽极低但非零

决策 2authToken 有效期

选择900 秒15 分钟API 允许的最小值)

理由:

  • 融合认证的典型交互在几秒到几十秒内完成
  • 更短的 Token 有效期意味着更小的被盗用风险窗口
  • 与项目中 access_token 1 小时、refresh_token 30 天的分级策略一致——authToken 作为临时凭证应有最短有效期

决策 3阿里云 SDK 客户端生命周期

选择:懒初始化单例模式

理由:

  • 阿里云 SDK 客户端是线程安全的,可复用
  • 避免每次请求创建新客户端的开销HTTP 连接池复用)
  • 与项目中 db 客户端的单例模式保持一致
  • 仅在配置了阿里云环境变量时才初始化,不影响其他认证方式

决策 4路由设计

选择:新增 /auth/fusion/token/auth/fusion/verify,独立于 /auth/phone

理由:

  • Flutter 客户端已在 FUSION_AUTH_INTEGRATION.md 第 5 节明确定义了这两个路径,服务端需对齐
  • /auth/fusion/ 作为独立命名空间,语义上更准确——融合认证不只是一键取号,而是由阿里云模板编排的多种认证方式(号码认证、短信、图形验证等)
  • /auth/phone 保留 501 占位,未来可用于非融合认证的原生短信验证码登录等场景

替代方案(未采纳):

  • POST /auth/phone/fusion-token + 修改 POST /auth/phone:路径层级不合理,融合认证不等同于"手机号登录"
  • /auth/phone 请求体中加 action: 'getToken' | 'verify':语义不够明确,增加请求体复杂度

决策 5账号关联扩展

选择:在 /auth/link 中支持 provider: 'phone'

理由:

  • 已有游客关联华为/Apple 的完整流程(account-link-service.ts
  • 手机号关联复用相同的 accountMigrations 表和幂等逻辑
  • 仅需扩展 linkSchemaprovider 枚举和 credential 结构

阿里云 API 参考

GetFusionAuthToken

项目
Action GetFusionAuthToken
授权 dypns:GetFusionAuthToken
Endpoint dypnsapi.aliyuncs.com

请求参数:

名称 类型 必填 描述 示例值
SchemeCode string 认证方案 Code FA1000*************201
Platform string 平台:AndroidiOS Android
PackageName string Android 必填 App 包名 com.example.test
PackageSign string Android 必填 App 包签名 47fcc************************278
BundleId string iOS 必填 App bundleId com.example.test
DurationSeconds long Token 有效时长(秒),范围 90043200 900

响应示例:

{
  "Message": "成功",
  "RequestId": "CC3BB6D2-2FDF-4321-9DCE-B38165CE4C47",
  "Model": "FKcksloqk***********jalEc+",
  "Code": "OK",
  "Success": true
}

VerifyWithFusionAuthToken

项目
Action VerifyWithFusionAuthToken
授权 dypns:VerifyWithFusionAuthToken
Endpoint dypnsapi.aliyuncs.com

请求参数:

名称 类型 必填 描述 示例值
VerifyToken string 客户端 SDK 完成认证后返回的 Token LD108enNdlsl*******sFLKCks1==

响应示例:

{
  "Message": "示例值",
  "RequestId": "CC3BB6D2-2FDF-4321-9DCE-B38165CE4C47",
  "Model": {
    "PhoneNumber": "18012341234",
    "VerifyResult": "PASS",
    "PhoneScore": 20
  },
  "Code": "OK",
  "Success": true
}

关键错误码:

HTTP 状态码 错误码 描述
400 SmsCodeVerifyFail 短信验证码失败(号码认证降级到短信时)
400 Throttling.System 接口被限流
400 VerifySchemeNotExist 认证方案不存在
400 SchemeNotPassed 认证方案未通过审核
400 Unsupported.Account 账号未开通号码认证服务
403 UnauthorizedOperation 权限校验失败
500 SystemError 系统异常

分步实施计划

Phase 1基础设施前置条件

# 任务 产出文件 验证标准
1.1 安装阿里云 SDK 依赖 package.json @alicloud/dypnsapi20170525@alicloud/openapi-client@alicloud/tea-util 安装成功
1.2 添加环境变量 src/utils/config.ts.env.example ALIYUN_ACCESS_KEY_IDALIYUN_ACCESS_KEY_SECRETALIYUN_FUSION_SCHEME_CODE 加入 Zod schema可选字段
1.3 类型检查通过 bun run typecheck 无错误

Phase 2服务层

# 任务 产出文件 验证标准
2.1 创建阿里云客户端封装 src/services/auth/fusion-auth-client.ts(新建) 懒初始化单例,配置从 config 读取
2.2 实现 getFusionAuthToken src/services/auth/fusion-auth-client.ts 接收平台参数,调用阿里云 API返回 Token 字符串
2.3 实现 verifyWithFusionAuthToken src/services/auth/fusion-auth-client.ts 接收 verifyToken调用阿里云 API返回 { phoneNumber, verifyResult }
2.4 实现 findOrCreatePhone src/services/auth/jwt.ts 遵循 findOrCreateGuest / findOrCreateHuawei 相同模式
2.5 类型检查通过 bun run typecheck 无错误

Phase 3路由层

# 任务 产出文件 验证标准
3.1 新增 POST /auth/fusion/token 路由 src/routes/auth.ts 接收 { platform, packageName?, packageSign?, bundleId? },调用 getFusionAuthToken,返回 { fusionAuthToken }
3.2 新增 POST /auth/fusion/verify 路由 src/routes/auth.ts 接收 { verifyToken },调用 verifyWithFusionAuthToken + findOrCreatePhone,返回标准 LoginResponse
3.3 扩展 /auth/link 支持 phone provider src/routes/auth.tssrc/services/auth/account-link-service.ts linkSchema.provider 增加 'phone'credential 结构适配
3.4 类型检查通过 bun run typecheck 无错误

Phase 4测试

# 任务 产出文件 验证标准
4.1 fusion-auth-client 单元测试 src/__tests__/services/fusion-auth.test.ts(新建) mock 阿里云 SDK测试正常流程和错误处理
4.2 findOrCreatePhone 单元测试 src/__tests__/services/auth.test.ts 测试新用户创建和已有用户查找
4.3 全量测试通过 bun run test 全部通过

Phase 5文档

# 任务 产出文件 验证标准
5.1 更新 API 文档 docs/api-reference.md 新增 POST /auth/fusion/tokenPOST /auth/fusion/verify 端点描述
5.2 更新 CLAUDE.md 项目结构 CLAUDE.md services/auth 目录列表新增 fusion-auth-client

新增环境变量

# .env 新增项(阿里云融合认证)
ALIYUN_ACCESS_KEY_ID=          # 阿里云 RAM 用户的 AccessKey ID
ALIYUN_ACCESS_KEY_SECRET=      # 阿里云 RAM 用户的 AccessKey Secret
ALIYUN_FUSION_SCHEME_CODE=     # 融合认证方案 Code在阿里云控制台创建

配置特点:

  • 三个变量均为可选——未配置时不影响游客、华为、Apple 等其他登录方式
  • 调用融合认证相关接口时检查配置,未配置则返回 NOT_IMPLEMENTEDSERVICE_UNAVAILABLE
  • AccessKey 通过 RAM 子用户授权,仅需 AliyunDypnsFullAccess 权限

新增 API 端点定义

路由路径与 Flutter 客户端 FUSION_AUTH_INTEGRATION.md 第 5 节保持一致。

POST /auth/fusion/token

认证:无 限流10 次/分钟

用途:为客户端融合认证 SDK 提供鉴权 Token。客户端在调用 FusionAuth.init(schemeCode, authToken) 时需要此 Token。对应阿里云 GetFusionAuthToken API。

请求:

{
  "platform": "Android",
  "packageName": "com.duoqi.app",
  "packageSign": "47fcc************************278"
}
{
  "platform": "iOS",
  "bundleId": "com.duoqi.app"
}
字段 类型 必填 说明
platform 'Android' | 'iOS' 客户端平台
packageName string Android 必填 App 包名
packageSign string Android 必填 App 包签名
bundleId string iOS 必填 App bundleId

响应:

{
  "success": true,
  "data": {
    "fusionAuthToken": "FKcksloqk***********jalEc+"
  },
  "error": null
}

错误响应(服务未配置):

{
  "success": false,
  "data": null,
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Fusion auth is not configured"
  }
}

POST /auth/fusion/verify

认证:无 限流10 次/分钟

用途:客户端融合认证 SDK 完成认证后(onVerifySuccess 事件),将运营商返回的 verifyToken 发送到此端点。服务端用 verifyToken 向阿里云换取手机号,完成用户查找或创建,签发 JWT。对应阿里云 VerifyWithFusionAuthToken API。

请求:

{
  "verifyToken": "LD108enNdlsl*******sFLKCks1=="
}
字段 类型 必填 说明
verifyToken string 客户端 SDK onVerifySuccess 事件返回的 Token

成功响应(同 /auth/guest

{
  "success": true,
  "data": {
    "user": {
      "id": "uuid",
      "nickname": null,
      "avatarUrl": null,
      "tier": "free"
    },
    "tokens": {
      "accessToken": "jwt",
      "refreshToken": "jwt"
    }
  },
  "error": null
}

错误响应(认证失败):

{
  "success": false,
  "data": null,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Phone verification failed"
  }
}

错误响应(服务未配置):

{
  "success": false,
  "data": null,
  "error": {
    "code": "SERVICE_UNAVAILABLE",
    "message": "Fusion auth is not configured"
  }
}

前置条件检查清单

在开始编码之前,需要以下准备工作:

  • 阿里云 RAM 子用户:创建 RAM 子用户,授予 AliyunDypnsFullAccess 权限,获取 AccessKey ID 和 Secret
  • 融合认证方案:在号码认证服务控制台创建融合认证方案,获取 SchemeCode
  • Android 签名信息:确认 App 包名 (PackageName) 和签名 (PackageSign)
  • iOS Bundle 信息:确认 App bundleId
  • Flutter 客户端状态:确认 duoqi-flutter 是否已集成融合认证 SDK

风险与注意事项

  1. 手机号隐私合规:存储用户手机号需符合《个人信息保护法》,建议在隐私政策中明确告知
  2. 运营商支持范围号码认证一键取号依赖运营商网关WiFi 环境可能降级到短信验证码
  3. 限流:阿里云 API 有自身限流(错误码 Throttling.System),服务端应做好重试和降级
  4. 错误码映射:阿里云的错误码需映射到项目统一的 error.code 体系
  5. 账号冲突:同一手机号可能已通过其他方式注册(如先游客后手机号),需考虑合并策略