Files
crmeb-mp/.trae/documents/mp_to_h5_token_design.md

6.0 KiB
Raw Permalink Blame History

方案:小程序跳转到 H5 页面携带 token

Summary

  • 目标:在微信小程序内通过 web-view 打开指定 H5 地址时,让 H5 自动获得与小程序一致的登录态(token),并在 H5 内完成 Vuex/缓存写入与用户信息同步。
  • 约束(来自现状与确认):打开方式为 web-view;当前不方便改后端接口;倾向直接携带 Bearer token。
  • 推荐策略:优先把 token 放到 URL 的 fragment(# 之后)以避免出现在 HTTP 请求与 Referer/服务端日志里;H5 启动时从 fragment 解析 token,写入 LOGIN_STATUS 缓存并拉取用户信息,随后清理 URL 中的 token。

Current State Analysis

  • 小程序打开 H5 的通用入口:utils/util.js: JumpPath,当 url 包含 http 时在 MP/APP-PLUS 走 web-view 页面:
  • H5 端已有“从 URL query 自动登录”的机制,但目前只处理 remote_token:
    • H5 App.vue:if (!isLogin && option.query.hasOwnProperty('remote_token')) this.remoteRegister(...)
    • App.vue
    • remoteRegister 调用 GET /api/remote_register(noAuth)换取 token 或跳转 URL:
  • token 使用方式:请求层统一在 utils/request.js 注入 header Authori-zation: Bearer <token>:
  • 登录态落地:store/modules/app.js: LOGIN 将 token 写入 Cache.set(LOGIN_STATUS, token, time);并可配合 getUserInfo() 写入 uid/userInfo:

Proposed Changes

1) 小程序侧:构造带 token 的 H5 URL(尽量放在 fragment)

  • 修改文件:utils/util.js
  • 修改点:在 JumpPath 的 url.indexOf("http") != -1 分支(MP/APP-PLUS)中,在跳转到 /pages/annex/web_view/index 之前对目标 url 做“携带 token”的包装。
  • 行为设计:
    • 若用户未登录(store.getters.isLogin 为 false 或 store.state.app.token 为空),直接走现有登录流程(可复用 libs/login.js: toLogin),不打开 H5。
    • 若已登录:
      • 优先把 token 附加在 hash 内:https://h5.xxx.com/#/some/path?token=<encoded>
      • 若目标 url 不含 #,则退化为 query:https://h5.xxx.com/some/path?token=<encoded>(存在服务端日志/Referer 风险,属于“不得不”的兼容路径)。
    • 在把 url 作为参数传给 web_view 页面时,对整段 url 做 encodeURIComponent,避免 token 中的特殊字符以及 & 被小程序路由二次解析。
  • 预计新增/调整函数(放在 util.js 内部):
    • appendTokenToUrl(rawUrl, token): string(只做字符串处理,不引入新依赖)
    • encodeForWebView(url): string(统一 encodeURIComponent)
  • 影响面:
    • 任何通过 this.$util.JumpPath('https://...') 打开的 H5 链接,都将自动带上 token(仅在 MP/APP-PLUS 生效)。

2) web-view 容器页:强制 decode 前提下保证传参完整

  • 修改文件:pages/annex/web_view/index.vue
  • 修改点:保持现有 decodeURIComponent(option.url),但要求所有调用方都传入 encode 后的 url。
  • 如果项目内存在“未 encode 直接拼接 url”的调用点(当前 utils/util.js 即如此),统一改为 encode 后再传。

3) H5 侧:解析 token → 写入登录态 → 拉取用户信息 → 清理 URL

  • 修改文件:App.vue
  • 修改点:H5 onLaunch 内(// #ifdef H5 分支)补充 token 自动登录逻辑,优先级建议:
    1. 若当前已登录(this.$store.getters.isLogin 为 true),不处理外部 token(避免覆盖用户主动登录态)。
    2. 仍保留现有 remote_token 分支逻辑(兼容已有能力)。
    3. 新增 token 分支逻辑:
      • 从 option.query.token 读取(query 方案)
      • 若取不到,再从 location.hash 里解析 token(fragment 方案)
      • 拿到 token 后:
        • this.$store.commit("LOGIN", { token, time: 0 })(time=0 表示缓存不主动过期,后续由服务端 410xxx 兜底踢下线)
        • 调用 getUserInfo(),成功后 SETUID/UPDATE_USERINFO
        • 最后用 history.replaceState 把 URL 中的 token 移除(避免用户复制链接时携带 token)
  • 需要补一个轻量解析方法(不引入库):
    • parseTokenFromHash(hash): string | '':兼容 #/path?token=... 和 #/?token=... 两种。

Assumptions & Decisions

  • H5 地址可能是 uni-app 默认 hash 路由(/#/);因此默认把 token 放在 fragment 内最安全。
  • 不改后端时,“直接 token”不可避免有泄露风险;通过 fragment + 清理 URL 将风险降到相对可接受:
    • fragment 不会出现在 HTTP 请求行与一般服务端 access log
    • Referer 通常不包含 fragment
  • token 仍可能在以下场景泄露:用户截屏、设备调试、前端错误上报采集 location.href 等;因此上线前需确认前端埋点/监控不会上报完整 URL。

Verification

  • 本地/测试环境:
    • 小程序端登录后,通过任意入口打开一个 H5 链接(走 JumpPath):
      • web-view 打开的 H5 页面无需再次登录
      • H5 内 store.getters.isLogin 为 true,且请求头带 Authori-zation: Bearer ...
      • getUserInfo() 可正常返回并写入 uid/userInfo
    • 打开后检查 H5 当前地址:token 参数已被移除(history.replaceState 生效)
  • 回归:
    • 未登录时打开 H5 链接:应先跳登录,不应打开带 token 的 H5
    • H5 原有 remote_token 登录不受影响