本指南面向拥有多个微信小程序和多个开发团队的公司。推荐让每个团队自行保管微信 AppSecret,SovereignID 只负责统一用户、绑定流程和面向业务后端的一次性登录票据。
“稳定”指该中心账号存续期间,用户修改邮箱、手机号、昵称,或新增另一个小程序 OpenID 时,sub 仍保持不变。未完成绑定的游客没有 sub;删除并重新创建账号也不是同一账号。示例中的 sub=42 仅用于说明,业务后端必须把 sub 当作不透明字符串,不能依赖其数值或格式。
业务后端应将 issuer(SovereignID 的固定发行方地址)和 sub 映射到自己的本地用户 ID;只接入一个发行方时可保存 sub,但不应用 OpenID、UnionID、手机号或昵称代替它。sub 用于识别,不是登录凭证或权限结论;业务后端仍须建立自己的会话并执行授权。
一、适用模式与职责边界
| 模式 | AppSecret 保存位置 | 适用情况 |
|---|---|---|
| 开发者自持 AppSecret(推荐) | 对应小程序的业务后端 | 多个团队自助接入;业务后端自行向微信验证 wx.login code,再向 SovereignID 断言已验证的 AppID 内 OpenID。 |
| SovereignID 托管 AppSecret(高级) | SovereignID,以加密形式保存 | 需要由 SovereignID 直接调用微信接口,例如微信手机号快速验证或网页扫码登录。 |
推荐模式中,业务后端是受信任边界:它持有微信 AppSecret 和 SovereignID Client Secret。因此必须将两类密钥放入服务端的密钥管理系统或环境变量,不能交给小程序、浏览器、日志、客服工单或截图。
二、没有微信开放平台时,如何归并同一用户
同一个微信用户在每个小程序中会得到不同 OpenID。身份记录始终以“AppID + OpenID”保存,不能跨小程序直接比较 OpenID。
SovereignID 用户 sub=42
├─ 小程序 A:appid=wxA,openid=oA
└─ 小程序 B:appid=wxB,openid=oB
- 用户首次进入小程序 A,验证手机号或邮箱;系统将 A 的 OpenID 绑定到 SovereignID 用户 sub=42。
- 用户首次进入小程序 B,由于没有可信 UnionID,仍需验证同一个已验证手机号或邮箱。
- 系统将 B 的 OpenID 也绑定到 sub=42;此后 A 与 B 的后端都会得到相同 sub。
三、开发者自助登记一个小程序
- 开发者先登录 SovereignID,并验证邮箱或手机号;这会成为小程序所有者的审计身份。
- 进入“账号 → 小程序管理 → 登记小程序”,填写名称和微信 AppID。AppID 是公开标识,但同一个 AppID 在系统内只能登记一次。
- 选择“开发者自持 AppSecret”,并按业务需要选择首次绑定策略和最小必要的资料字段。
- 创建成功后立即保存 public_id、Client ID 和只显示一次的 Client Secret。public_id 可写入小程序;Client Secret 只能写入业务后端。
- 在微信公众平台分别配置 SovereignID HTTPS 域名和业务后端 HTTPS 域名为 request 合法域名。开发者工具的本地调试地址不能替代真机域名配置。
自助登记适用于受邀请的内部开发者。由于推荐模式不会把 AppSecret 交给 SovereignID,系统无法在登记瞬间自动证明 AppID 的微信所有权;管理员应保留所有权转移、全局停用和异常审计能力,不应向任意外部注册用户开放抢注。
四、业务后端验证微信身份并断言给 SovereignID
小程序不可直接调用受信身份断言端点。它只将 wx.login 的临时 code 发送给自己的业务后端;后端使用本地 AppID、AppSecret 调用微信 jscode2session,取得当前 AppID 范围内的 OpenID。
POST <ISSUER>/api/v1/mini-programs/<public_id>/delegated-logins/start
Authorization: Basic base64(<client_id>:<client_secret>)
Content-Type: application/json
{"openid":"微信已验证的 AppID 内 OpenID"}
此请求只能从业务后端发起。SovereignID 会验证 Client ID 与 Client Secret 是否属于当前 public_id,并且不会在公开响应、应用日志或审计页面回显 OpenID。
| 响应状态 | 业务后端应做什么 |
|---|---|
authenticated | 立即在服务端兑换 login_ticket,并用返回的 sub 建立或恢复本地用户会话。 |
binding_required | 可引导用户验证手机号或邮箱;也可建立仅限低风险功能的本地游客会话,稍后从个人中心重新发起绑定。 |
access_denied | 不要向用户推断账号、用户组或联系方式是否存在;按业务产品策略展示通用拒绝提示。 |
五、兑换一次性票据并创建本地会话
一次性登录票据兑换端点:
POST <ISSUER>/api/v1/mini-program/tickets/exchange
Authorization: Basic base64(<client_id>:<client_secret>)
Content-Type: application/json
{"login_ticket":"eyJ..."}
成功响应固定包含 sub、auth_time 与 amr;创建小程序时授权的 profile、email、phone 会按需附带。业务数据应以稳定 sub 建立关联,而不是以昵称、手机号或 OpenID 作为跨应用主键。
- 登录票据的默认有效期为 60 秒,只能成功兑换一次。
- 票据的 audience 绑定到创建它的小程序业务后端客户端;小程序 B 的 Client Secret 不能兑换小程序 A 的票据。
- 业务后端兑换成功后,应签发自己的不透明会话令牌或安全 Cookie;不要把 SovereignID 登录票据当作长期业务会话。
六、延迟绑定与游客体验
当用户首次拒绝提供手机号或邮箱时,推荐的业务后端行为如下:
- 以当前小程序 OpenID 创建或恢复本地游客身份,仅开放浏览、试用等低风险能力。
- 保存当前会话与本地游客身份的关联,不要创建没有联系方式的正式 SovereignID 用户。
- 用户稍后点击“绑定账号”时,让小程序重新 wx.login,业务后端重新向 SovereignID 发起受信身份断言,取得新的 binding_token。
- 完成短信或邮箱验证后,将返回的 login_ticket 交给业务后端兑换,并把原本的本地游客身份关联到返回的 sub。
验证流程使用 binding_token 和短时验证码;过期后重新发起即可。绑定成功不是数据迁移:业务记录可以继续使用原本的本地用户 ID,只需补充 SovereignID sub 关联。
业务后端应保存自己发起的 binding_token 摘要,并在兑换绑定完成后的票据时核对返回的 binding_token_digest。这样该票据只能升级发起这次挑战的游客会话,不能被误关联到另一位游客。配套 Django 示例已实现此校验。
七、首次绑定接口
当受信身份断言返回 binding_required 时,小程序可按 available_methods 显示短信或邮箱方式。开发者自持 AppSecret 模式默认不会返回微信手机号快速验证。
POST <ISSUER>/api/v1/mini-programs/<public_id>/bindings/sms/request
{"binding_token":"...","identifier":"+8613812345678"}
POST <ISSUER>/api/v1/mini-programs/<public_id>/bindings/sms/confirm
{"binding_token":"...","verification_id":"...","code":"123456"}
邮箱流程将路径中的 sms 替换为 email。确认成功后,小程序获得一次性 login_ticket;必须把它交给业务后端兑换,不能在小程序内解析、保存或直接信任。
八、Django 示例与上线检查
配套示例为 SI_MiniProgramExample,它演示业务后端如何保管微信 AppSecret 与 Client Secret、验证 wx.login code、创建游客会话、兑换登录票据,以及在延迟绑定后将游客身份升级为中心用户。
- 为每个小程序创建独立 Client Secret;开发者离开、后端泄露或异常调用时,立即在小程序详情页重置该密钥并停用小程序。
- 验证用户先在 A、再在 B 中使用同一手机号或邮箱完成首次关联;两次兑换应获得同一个 sub。
- 验证同一 login_ticket 的第二次兑换返回 invalid_ticket。
- 验证用户拒绝绑定时只能进入游客能力,完成绑定后原有业务数据仍可见。
- 检查小程序代码、构建产物、日志、监控、客服截图和错误上报中均不存在 AppSecret、Client Secret、session_key、完整 OpenID、验证码或完整手机号。
九、何时使用托管 AppSecret 高级模式
如果团队明确同意由 SovereignID 托管 AppSecret,可在登记时选择托管模式。此时小程序可将 wx.login code 直接提交给:
POST <ISSUER>/api/v1/mini-programs/<public_id>/login
{"code":"wx.login 返回的临时 code"}
托管模式会把 AppSecret 加密保存,并且才可启用微信手机号快速验证、网页扫码等需要 SovereignID 调用微信接口的能力。它是可选高级模式,不是多团队自助接入的默认前提。