6.6 KiB
6.6 KiB
Axum 微信登录接入设计
日期:2026-04-21
1. 文档目的
这份文档用于把 Rust api-server 当前阶段的微信登录落地边界固定到可编码级别,避免在接入 SpacetimeDB 前把“第三方 provider 身份”和“系统内登录态 JWT”混成一层。
本次设计固定以下结论:
- 微信不能作为
SpacetimeDB直连时直接消费的 OIDC provider。 - 微信返回的
code -> access_token/openid/unionid只能先在Axum侧完成兑换。 Axum在拿到微信身份后,签发本系统自己的 OIDC 兼容 JWT。- 前端、Axum、未来 SpacetimeDB 都统一消费这枚系统 JWT,而不是直接消费微信 token。
2. 核心边界
2.1 为什么不能直接把微信 token 给 SpacetimeDB
原因固定为:
- 当前微信开放平台登录链路不是标准 OIDC
id_token语义。 - 常见返回物是
code、access_token、openid、unionid,不是可直接按 OIDCiss/sub/aud校验的标准 JWT。 SpacetimeDB当前直接接受的是 OIDC 兼容 JWT,而不是微信的 provider 原始返回。
因此正式口径固定为:
- 微信只负责提供三方身份。
- 系统内部登录态统一由
Axum签发。 - 未来接入
SpacetimeDB时,传给.withToken(...)的仍然是本系统 JWT。
2.2 与 SpacetimeDB 的关系
本阶段虽然前端仍然不直连 SpacetimeDB,但 JWT 语义必须从现在就和后续保持一致:
iss固定为本系统网关发行者。sub固定为系统内稳定用户 ID,不允许使用手机号、openid、unionid。provider表示当前会话登录来源,允许为password、phone、wechat。binding_status表示当前账号是否仍处于pending_bind_phone。
3. 登录主链
3.1 GET /api/auth/wechat/start
职责固定为:
- 归一化
redirectPath - 按
User-Agent判断授权场景:desktopwechat_in_app
- 创建一次性
state - 返回微信授权地址
关键约束:
- 普通手机浏览器且非微信内打开时,不创建 state,直接报错。
- 每次点击微信登录都创建新 state,不复用旧 state。
3.2 GET /api/auth/wechat/callback
职责固定为:
- 先消费一次性
state - 再用
code或 mock code 换取微信身份 - 先按
unionid,再按openid查系统内是否已有绑定账号 - 若没有账号,则创建
pending_bind_phone的微信壳账号 - 若本次是按
unionid命中,但微信回调带回了新的openid,必须把新的openid -> user_id映射一并回写 - 签发系统 access token
- 创建 refresh session
- 以 hash 片段回跳前端:
auth_provider=wechatauth_token=...auth_binding_status=active|pending_bind_phone
3.3 POST /api/auth/wechat/bind-phone
职责固定为:
- 仅允许当前 Bearer 用户为
pending_bind_phone - 校验手机号验证码
- 若手机号已对应正式账号:
- 把微信身份归并到该正式账号
- 删除当前微信壳账号
- 若手机号尚未绑定:
- 激活当前微信账号
- 写入手机号与掩码
- 再次签发 access token 与 refresh session
补充约束:
- 若绑定的是当前待绑定微信账号本体,则返回用户快照的
loginMethod = wechat。 - 若是并入已有手机号正式账号,则返回目标正式账号快照,当前实现会保持其账号主登录方式,例如
loginMethod = phone。 - 但 access token 中的
provider仍按本次登录来源签发,不依赖账号主登录方式推断,因此微信绑定后的当前会话仍会签发provider = wechat。
3.4 POST /api/auth/wechat/miniprogram-login
职责固定为:
- 接收微信小程序原生壳通过
wx.login拿到的code。 - 在
Axum内调用微信jscode2session,兑换openid/unionid。 - 复用
resolve_login处理unionid/openid -> user_id的查找、补写和待绑定账号创建。 - 签发本系统 access token,并创建 refresh session。
- 返回:
tokenbindingStatususer
关键约束:
- 小程序壳不能把裸
openid直接拼给 H5 做登录。 - H5 仍只消费本系统
auth_token,小程序壳只是把这枚 token 放入既有 hash 回调格式。 - 小程序请求必须补传
x-client-type=mini_program与x-client-runtime=wechat_mini_program,用于 refresh session 记录来源。
4. 当前最小实现策略
当前阶段为了先打通 Rust 后端闭环,采用以下最小实现:
module-auth内使用进程内存仓模拟:- 用户
- refresh session
- 微信 identity
- 微信 state
- 手机验证码
- 短信验证码先走 mock:
- 固定验证码
123456 - TTL
5分钟 - 冷却
60秒
- 固定验证码
- 微信 provider 同时支持:
mockreal
这意味着:
- 本轮目的是先把 Rust 认证主链补齐。
- 后续切到真实
SpacetimeDB private table时,保留接口 contract,不改前端页面。
5. 当前接口影响面
本次接入会补齐这些 Rust 接口:
GET /api/auth/login-optionsPOST /api/auth/phone/send-codePOST /api/auth/phone/loginGET /api/auth/wechat/startGET /api/auth/wechat/callbackPOST /api/auth/wechat/bind-phonePOST /api/auth/wechat/miniprogram-login
6. 环境变量
当前 Rust api-server 需要新增或明确这些变量:
WECHAT_AUTH_ENABLEDWECHAT_AUTH_PROVIDERWECHAT_APP_IDWECHAT_APP_SECRETWECHAT_CALLBACK_PATHWECHAT_REDIRECT_PATHWECHAT_AUTHORIZE_ENDPOINTWECHAT_ACCESS_TOKEN_ENDPOINTWECHAT_USER_INFO_ENDPOINTWECHAT_JS_CODE_SESSION_ENDPOINTWECHAT_MINI_PROGRAM_APP_IDWECHAT_MINI_PROGRAM_APP_SECRETWECHAT_STATE_TTL_MINUTESWECHAT_MOCK_USER_IDWECHAT_MOCK_UNION_IDWECHAT_MOCK_DISPLAY_NAMEWECHAT_MOCK_AVATAR_URL
7. 与后续 SpacetimeDB 的衔接要求
未来把认证真相从内存仓切到 SpacetimeDB 时,必须继续保持:
wechat_auth_state只做一次性 OAuth 状态,不存 provider token。auth_identity负责openid/unionid -> user_id绑定。refresh_session负责设备会话。Axum负责签 JWT。SpacetimeDB module只信系统 JWT claims,不直接解析微信回调结果。
8. 一句话结论
微信登录在本项目中的正确接法不是“把微信 token 直接接到 SpacetimeDB”,而是:
微信只提供三方身份,Axum 负责完成 provider 兑换并签发系统 OIDC 兼容 JWT,再把这枚 JWT 用作前端与未来 SpacetimeDB 的统一登录态。