# 017 - Apple / Google / LINE 三方登录 ## 背景 C 端 app 现仅支持「手机号 + 短信验证码」登录(`/infouser/user/lodeing`,登录即注册)。本特性新增 Apple / Google / LINE 三方登录,降低台湾市场获客与登录摩擦。鉴权沿用现有自写 `JwtUtil`(HS256 JWT + Redis 会话),不引入新鉴权体系。 ## 锁定的关键决策(业务确认) 1. **手机号始终是主键**:三方账号只是绑在「某个手机号账号」上的快捷入口,不在 InfoUser 上落三方 ID(独立表 `info_user_oauth` 记录绑定关系)。 2. **首次使用任一三方登录强制手机验证**:拿到手机号后查 `info_user`——已注册则**关联**到该老账号(写一条 oauth 绑定),未注册则**新建**账号后再绑定。**不以邮箱做关联**。 3. **不使用 Apple 邮箱信息**:Apple 只取 identityToken 的 `sub`(稳定用户ID),忽略 email。 4. **记录登录渠道 loginType**:因一个用户可绑多家三方,loginType 不落用户表,而写入**本次会话的 JWT claim**(`provider`)。 ## User Story ### US1 - 三方登录入口 (P0) 客户在登录页选 Apple/Google/LINE,客户端拿到 provider 凭证后调后端校验。 ### US2 - 首次登录手机验证 + 绑定 (P0) 首次用某三方账号登录时,强制输手机号 + 短信验证码:手机号已注册→关联该账号;未注册→新建账号;随后建立 oauth 绑定。 ### US3 - 已绑定快捷登录 (P0) 已绑过该三方的账号再次登录,校验凭证通过即直接签发 token,免手机验证。 ## Functional Requirements - **FR-001**: 提供 `POST /infouser/user/oauthLogin {provider, credential, ...}`,后端校验 provider 凭证换取稳定 providerUid。 - **FR-002**: 凭证校验三选一:Apple=验 ES256 identityToken 取 sub;Google=tokeninfo HTTP 验真取 sub 并校 audience;LINE=前端传授权 code,后端用 code+clientSecret+clientId 向 `oauth2/v2.1/token` 换 access_token,再调 v2/profile 取 userId(Authorization Code 流程,不直接收前端 accessToken)。 - **FR-003**: 按 (provider, providerUid) 查 `info_user_oauth`:命中→校验用户 status/del_flag 正常后直接签发 token(claim `provider`)返回;未命中→缓存 {provider,providerUid} 到 Redis(短TTL),返回 `needPhone` + tempKey。 - **FR-004**: 提供 `POST /infouser/user/oauthBindPhone {tempKey, phone, code, ...}`:取回缓存的 providerUid + 验短信码(复用 lodeing 逻辑,含万能码 8888)→ `getuser(phone)`:已注册→关联;未注册→`createUser` 新建;随后 insert `info_user_oauth(user_id, provider, provider_uid)`。 - **FR-005**: 新建用户的 `info_user.nick_name` 统一用手机号(与现有 createUser 一致),avatar 留空;三方资料不得覆盖用户主昵称/头像。第三方名称的独立展示见下方 2026-09-24 后续增量。 - **FR-006**: token claim 增加 `provider` 字段(apple/google/line;手机号登录为 phone),供登录渠道统计或「未绑手机限制」类约束使用。 - **FR-007**: 登录端点 `@Anonymous` 放行;受保护接口继续走 `@Auth`,零额外接入。 - **FR-008**: 凭证校验失败 / tempKey 过期 / 短信码错误 → 明确错误提示,不签发 token、不建账号。 - **FR-009**(2026-08-05 增量): LINE「唤起 LINE App / 系统浏览器」流程下前端拿不到 code,新增服务端回调 `GET /auth/line/callback`(@Anonymous,LineCallbackController):接 LINE 重定向的 code → 复用 verify("line",code) 换 token 取 userId → 已绑定签 token / 未绑定生成 tempKey → 302 跳回 App scheme `oauth.line.app-redirect`(`?token=` / `?needPhone=1&tempKey=` / `?error=`,未绑定时 App 再调 /infouser/user/oauthBindPhone)。原 /oauthLogin 保留,覆盖前端自取 code 场景(H5/webview/SDK)。约束:oauth.line.redirect-uri 须与 LINE Console 回调白名单一致(否则 400 redirect_uri_mismatch)。 ## Key Entities - **info_user_oauth(新增)**:`id`、`user_id`、`provider`(apple/google/line)、`provider_uid`、`create_time`。`UNIQUE(provider, provider_uid)` 用于按三方ID反查用户;`user_id` 普通索引。 - **InfoUser(已有,不改)**:仍是手机号为主键,无三方 ID 列。 ## 安全要点 - Apple/Google 必须校验 audience(= 我方 client_id/bundleId),防其他 App 的 token 被冒用。 - Apple 校验 iss=`https://appleid.apple.com` + exp。 - tempKey 一次性、短 TTL(5 分钟),防重放。 ## 不在本期范围 - 后台管理端解绑/查看三方绑定(后续增量)。 - 第三家以上三方(微信/Facebook)——表结构已可扩展,但本期只接 Apple/Google/LINE。 - Apple 隐藏邮箱中转、refresh_token 续期等高级场景。 ## 2026-09-24 后续增量:登录渠道与第三方名称展示(待实施) - 用户端分别展示原有 `info_user.nick_name`、本次登录渠道(Apple / LINE)及该渠道的第三方名称。用户修改昵称仍走原有资料修改流程;切换登录渠道不得覆盖 `info_user.nick_name`,也不得把不同 `user_id` 的三方身份视为同一账号。 - `info_user_oauth.user_id` 只表示账号绑定关系,不表示“最近一次登录”。当前 `provider` 记录在本次登录的 JWT 会话中,`info_user` 不保存最近登录渠道;本增量展示的是本次会话的登录方式,不是跨设备共享的历史“最后一次登录方式”。 - 第三方名称按绑定身份独立保存于 `info_user_oauth.provider_display_name`(新增可空字段);本次登录为 LINE 时显示 LINE 名称,为 Apple 时显示 Apple 名称,不使用其他渠道的名称。当前表尚无此字段,待实施后才可用。 - LINE:每次成功登录且取得带 `profile` 权限的资料时,读取 `displayName` 并刷新对应绑定记录;获取失败时保留已保存的名称,不影响登录。 - Apple:首次授权取得用户共享的 `fullName` 时保存到对应绑定记录;后续登录不要求再次取得名称。历史账号未保存过 Apple 名称或用户未共享名称时,该名称允许为空,用户端仅展示“Apple 登录”及原有昵称,不伪造 Apple 名称。 - 本增量仅记录需求,尚未修改 App、后端、数据库或现有账号数据;实施时须同步更新接口契约、数据模型、SQL 和验证用例。