重写
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
# 微信登录真实联调手册
|
||||
|
||||
日期:`2026-04-21`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于把当前仓库里的微信登录,从“代码已具备 mock / real 双模式”推进到“开发、测试、部署都能按步骤联调”的级别。
|
||||
|
||||
文档目标不是重复实现设计,而是回答以下落地问题:
|
||||
|
||||
1. 本地先怎么用 mock 跑通
|
||||
2. 什么时候切 `real`
|
||||
3. 真实微信开放平台需要配什么
|
||||
4. 回调地址到底怎么拼
|
||||
5. 前后端各自怎么验证
|
||||
6. 失败时先查哪一层
|
||||
|
||||
## 2. 当前实现结论
|
||||
|
||||
当前仓库里的微信登录实现固定为两段式:
|
||||
|
||||
1. 微信 OAuth 只负责返回第三方身份:`code -> openid / unionid`
|
||||
2. Rust `api-server` 负责把该第三方身份换成本系统 JWT
|
||||
|
||||
因此当前正式口径固定为:
|
||||
|
||||
1. 前端不直接消费微信 token
|
||||
2. `SpacetimeDB` 未来也不直接消费微信 token
|
||||
3. 统一由 `api-server` 签发系统 JWT,再回给前端和后续服务层
|
||||
|
||||
## 3. 模式说明
|
||||
|
||||
### 3.1 mock 模式
|
||||
|
||||
适用场景:
|
||||
|
||||
1. 本地前后端先验证交互链路
|
||||
2. 还没有微信开放平台配置
|
||||
3. 还没有可用公网回调域名
|
||||
|
||||
当前配置:
|
||||
|
||||
```env
|
||||
WECHAT_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_PROVIDER="mock"
|
||||
WECHAT_CALLBACK_PATH="/api/auth/wechat/callback"
|
||||
WECHAT_REDIRECT_PATH="/"
|
||||
WECHAT_MOCK_USER_ID="wx-mock-user"
|
||||
WECHAT_MOCK_UNION_ID="wx-mock-union"
|
||||
WECHAT_MOCK_DISPLAY_NAME="微信旅人"
|
||||
WECHAT_MOCK_AVATAR_URL=""
|
||||
```
|
||||
|
||||
mock 模式行为固定为:
|
||||
|
||||
1. `GET /api/auth/wechat/start` 仍会创建一次性 `state`
|
||||
2. 返回的 `authorizationUrl` 不会跳去微信,而是直接指向本地 callback
|
||||
3. callback 会走 mock 身份:`mock_code + state`
|
||||
4. 后端仍会完整创建微信登录态、refresh session 与待绑定账号
|
||||
|
||||
### 3.2 real 模式
|
||||
|
||||
适用场景:
|
||||
|
||||
1. 已拿到微信开放平台应用
|
||||
2. 已有可访问的公网域名
|
||||
3. 已在微信开放平台后台配置回调域名
|
||||
|
||||
当前配置:
|
||||
|
||||
```env
|
||||
WECHAT_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_PROVIDER="real"
|
||||
WECHAT_APP_ID="你的微信开放平台 AppID"
|
||||
WECHAT_APP_SECRET="你的微信开放平台 AppSecret"
|
||||
WECHAT_CALLBACK_PATH="/api/auth/wechat/callback"
|
||||
WECHAT_REDIRECT_PATH="/"
|
||||
```
|
||||
|
||||
real 模式行为固定为:
|
||||
|
||||
1. `GET /api/auth/wechat/start` 返回微信真实授权地址
|
||||
2. 用户在微信侧完成授权
|
||||
3. 微信回跳到 `WECHAT_CALLBACK_PATH`
|
||||
4. 后端用 `code` 换 `access_token/openid/unionid`
|
||||
5. 后端签发本系统 JWT,并把结果通过 URL hash 回跳给前端
|
||||
|
||||
## 4. 环境变量清单
|
||||
|
||||
当前真实联调至少需要这些变量:
|
||||
|
||||
| 变量 | 必填 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `WECHAT_AUTH_ENABLED` | 是 | 是否启用微信登录 |
|
||||
| `WECHAT_AUTH_PROVIDER` | 是 | `mock` 或 `real` |
|
||||
| `WECHAT_APP_ID` | `real` 模式必填 | 微信开放平台应用 ID |
|
||||
| `WECHAT_APP_SECRET` | `real` 模式必填 | 微信开放平台应用 Secret |
|
||||
| `WECHAT_CALLBACK_PATH` | 是 | 回调路径,默认 `/api/auth/wechat/callback` |
|
||||
| `WECHAT_REDIRECT_PATH` | 是 | 登录完成后前端默认落点 |
|
||||
| `WECHAT_AUTHORIZE_ENDPOINT` | 否 | 默认桌面二维码授权地址 |
|
||||
| `WECHAT_ACCESS_TOKEN_ENDPOINT` | 否 | 默认 access_token 接口 |
|
||||
| `WECHAT_USER_INFO_ENDPOINT` | 否 | 默认用户信息接口 |
|
||||
| `WECHAT_STATE_TTL_MINUTES` | 否 | state 有效期,默认 `15` 分钟 |
|
||||
|
||||
补充说明:
|
||||
|
||||
1. `WECHAT_CALLBACK_PATH` 只是路径,不是完整 URL。
|
||||
2. 当前完整回调地址由后端在运行时按请求头拼接:
|
||||
- 优先 `x-forwarded-proto`
|
||||
- 其次 `host` / `x-forwarded-host`
|
||||
3. 因此部署到反向代理后,代理层必须正确透传 `host` 与 `x-forwarded-proto`。
|
||||
|
||||
## 5. 微信开放平台后台配置
|
||||
|
||||
真实联调前,需要先在微信开放平台完成以下配置:
|
||||
|
||||
1. 创建网站应用并拿到 `AppID / AppSecret`
|
||||
2. 配置网站授权回调域名
|
||||
3. 确保回调域名与实际访问域名一致
|
||||
|
||||
当前项目必须特别注意:
|
||||
|
||||
1. 微信后台通常配置的是“域名”,不是完整路径
|
||||
2. 但项目真正收到回调时会落到:
|
||||
|
||||
```text
|
||||
https://你的域名/api/auth/wechat/callback
|
||||
```
|
||||
|
||||
3. 如果代理层把 HTTPS 终止在上游网关,必须把 `x-forwarded-proto=https` 正确传给 `api-server`
|
||||
4. 否则后端可能生成 `http://.../api/auth/wechat/callback`,导致微信侧回调地址不匹配
|
||||
|
||||
## 6. 本地 mock 联调步骤
|
||||
|
||||
### 6.1 配置
|
||||
|
||||
在 `.env.local` 中写入:
|
||||
|
||||
```env
|
||||
SMS_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_PROVIDER="mock"
|
||||
VITE_AUTH_ALLOW_DEV_GUEST="false"
|
||||
```
|
||||
|
||||
### 6.2 启动
|
||||
|
||||
前端和后端分别启动当前项目自己的开发链路。
|
||||
|
||||
若只验证 Rust 后端,可直接启动 `server-rs` 的 `api-server`。
|
||||
|
||||
### 6.3 验证顺序
|
||||
|
||||
1. 请求 `GET /api/auth/login-options`
|
||||
2. 期望返回同时包含:
|
||||
- `phone`
|
||||
- `wechat`
|
||||
3. 前端点击“微信登录”
|
||||
4. 浏览器应先跳到 `/api/auth/wechat/start`
|
||||
5. 随后直接命中本地 `/api/auth/wechat/callback?...`
|
||||
6. 前端收到 hash:
|
||||
- `auth_provider=wechat`
|
||||
- `auth_token=...`
|
||||
- `auth_binding_status=pending_bind_phone`
|
||||
7. 页面进入“绑定手机号”界面
|
||||
8. 发送验证码并绑定手机号
|
||||
9. 若手机号未使用,当前账号激活
|
||||
10. 若手机号已对应正式账号,微信身份并入已有正式账号
|
||||
|
||||
## 7. 真实微信联调步骤
|
||||
|
||||
### 7.1 切换配置
|
||||
|
||||
在 `.env.local` 中切到:
|
||||
|
||||
```env
|
||||
SMS_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_ENABLED="true"
|
||||
WECHAT_AUTH_PROVIDER="real"
|
||||
WECHAT_APP_ID="你的 AppID"
|
||||
WECHAT_APP_SECRET="你的 AppSecret"
|
||||
WECHAT_CALLBACK_PATH="/api/auth/wechat/callback"
|
||||
WECHAT_REDIRECT_PATH="/"
|
||||
VITE_AUTH_ALLOW_DEV_GUEST="false"
|
||||
```
|
||||
|
||||
### 7.2 部署要求
|
||||
|
||||
需要有一个用户浏览器能够访问的地址,例如:
|
||||
|
||||
```text
|
||||
https://game.example.com
|
||||
```
|
||||
|
||||
并且该地址最终把认证请求转发到当前 Rust `api-server`。
|
||||
|
||||
### 7.3 代理层要求
|
||||
|
||||
反向代理必须至少透传:
|
||||
|
||||
1. `Host`
|
||||
2. `X-Forwarded-Proto`
|
||||
3. `X-Forwarded-Host`(如果你的代理链路会改写 Host)
|
||||
|
||||
### 7.4 验证顺序
|
||||
|
||||
1. 浏览器访问前端页面
|
||||
2. 点击“微信登录”
|
||||
3. 观察 `/api/auth/wechat/start` 返回的 `authorizationUrl`
|
||||
4. 确认其中 `redirect_uri` 指向真实回调地址
|
||||
5. 在微信授权完成后,确认请求回到:
|
||||
|
||||
```text
|
||||
/api/auth/wechat/callback?code=...&state=...
|
||||
```
|
||||
|
||||
6. 观察回跳前端地址是否带上:
|
||||
- `auth_provider=wechat`
|
||||
- `auth_token=...`
|
||||
- `auth_binding_status=active|pending_bind_phone`
|
||||
7. 如果进入待绑定页面,继续完成手机号绑定
|
||||
8. 绑定后再请求 `GET /api/auth/me`
|
||||
9. 确认:
|
||||
- 当前用户存在
|
||||
- `wechatBound = true`
|
||||
- `bindingStatus` 已更新为目标状态
|
||||
|
||||
## 8. 账号命中规则
|
||||
|
||||
当前实现固定按以下顺序命中已有账号:
|
||||
|
||||
1. 先按 `unionid`
|
||||
2. 再按 `openid`
|
||||
3. 都没有命中时,创建 `pending_bind_phone` 微信壳账号
|
||||
|
||||
补充规则:
|
||||
|
||||
1. 若按 `unionid` 命中了已有微信身份,但本次微信回调带来了新的 `openid`,后端会把新的 `openid -> user_id` 映射补齐
|
||||
2. 若后续绑定手机号时发现该手机号已经属于正式账号,则会把微信身份并入这个正式账号
|
||||
|
||||
## 9. 前端验收点
|
||||
|
||||
前端联调时至少检查以下行为:
|
||||
|
||||
1. 登录弹窗只在 `login-options` 返回 `wechat` 时显示“微信登录”按钮
|
||||
2. 点击微信登录后应跳去后端返回的 `authorizationUrl`
|
||||
3. 回调 hash 被前端消费后,应把 `auth_token` 存入本地登录态
|
||||
4. 若 `auth_binding_status=pending_bind_phone`,页面必须进入绑定手机号界面
|
||||
5. 绑定成功后,应切回正常已登录状态
|
||||
|
||||
## 10. 后端验收点
|
||||
|
||||
当前后端至少应满足以下检查:
|
||||
|
||||
1. `GET /api/auth/wechat/start` 能返回授权地址
|
||||
2. `GET /api/auth/wechat/callback` 能创建系统会话并回跳
|
||||
3. `POST /api/auth/wechat/bind-phone` 能完成补绑
|
||||
4. `GET /api/auth/me` 能反映最新 `bindingStatus / wechatBound`
|
||||
5. access token claims 中的 `provider` 应保持当前会话来源为 `wechat`
|
||||
|
||||
## 11. 常见失败点
|
||||
|
||||
### 11.1 点微信登录后直接报“微信登录暂未启用”
|
||||
|
||||
先检查:
|
||||
|
||||
1. `WECHAT_AUTH_ENABLED` 是否为 `true`
|
||||
2. 运行的是否真是 Rust `api-server`
|
||||
3. 前端是否还在打旧 Node 服务
|
||||
|
||||
### 11.2 微信授权页能打开,但回调报错
|
||||
|
||||
先检查:
|
||||
|
||||
1. 微信后台配置的回调域名是否正确
|
||||
2. 代理层是否正确透传 `host` 与 `x-forwarded-proto`
|
||||
3. `WECHAT_CALLBACK_PATH` 是否与当前代码路径一致
|
||||
|
||||
### 11.3 按 `unionid` 命中后又出现新壳账号
|
||||
|
||||
先检查:
|
||||
|
||||
1. 微信开放平台当前应用是否真的能返回稳定 `unionid`
|
||||
2. 当前账号历史上是否只有 `openid` 没有 `unionid`
|
||||
3. 是否发生了不同开放平台主体之间的数据割裂
|
||||
|
||||
### 11.4 绑定手机号后命中了已有正式账号,但前端看到 `loginMethod=phone`
|
||||
|
||||
这是当前实现允许的结果。
|
||||
|
||||
原因是:
|
||||
|
||||
1. 返回的是目标正式账号快照
|
||||
2. 目标正式账号本身的主登录方式仍可能是 `phone`
|
||||
3. 但当前会话签发的 access token `provider` 仍然是 `wechat`
|
||||
|
||||
## 12. 当前自动化验证证据
|
||||
|
||||
当前仓库里已经有这些自动化验证:
|
||||
|
||||
1. `cargo test -p api-server`
|
||||
覆盖:
|
||||
- `wechat/start`
|
||||
- `wechat/callback`
|
||||
- `wechat/bind-phone`
|
||||
2. `cargo test -p module-auth`
|
||||
覆盖:
|
||||
- `unionid` 优先命中已有微信用户
|
||||
- 微信待绑定账号并入已有手机号正式账号
|
||||
3. `npm test -- --run src/services/authService.test.ts`
|
||||
覆盖:
|
||||
- 前端微信登录起跳
|
||||
- callback hash 消费
|
||||
|
||||
## 13. 一句话切换原则
|
||||
|
||||
本地先用 `mock` 跑通页面和会话闭环,公网域名与微信后台配置就绪后,再切 `real` 做真实 OAuth 联调。
|
||||
Reference in New Issue
Block a user