小程序 web-view 内嵌 H5 的会话管理:Token 失效跳转登录的完整方案

前言

在小程序开发中,<web-view> 组件是承载 H5 页面的重要载体。但小程序和 H5 有各自独立的 Storage,token 并不互通。当 H5 页面的 token 失效时,如何让用户顺利跳转到小程序原生登录页,并在登录成功后原路返回?

本文将以实际项目为例,分享一套完整的跨端会话管理方案,涵盖:

  • Token 失效时从 H5 跳转小程序登录页
  • 登录成功后携带参数原路返回
  • 登录页返回按钮的智能控制

一、问题背景

1.1 会话隔离问题

H5 页面通过小程序 <web-view> 内嵌运行时,存在一个典型的跨端会话隔离问题:

存储 作用域 特点
小程序 Storage 小程序进程 独立存储,生命周期跟随小程序
H5 Storage web-view 进程 独立存储,与小程序不互通

核心矛盾 :H5 页面 token 失效时,无法通过 H5 页面直接跳转到小程序原生登录页(wx.miniProgram API 在 web-view 中访问受限),用户被"困在" web-view 里。

1.2 期望的用户旅程

css 复制代码
H5 活动页 → token 失效 → Toast 提示 → 跳转小程序登录页 
    → 登录成功 → 携带参数返回 H5 页面 → 正常使用

二、Token 失效跳转小程序登录

2.1 核心方法

javascript 复制代码
// pages/webH5View/index.vue
noValidTokenGoMiniProgramLogin() {
  // 1. Toast 提示用户即将跳转
  uni.showToast({
    title: '登录过期,即将进入登录页面',
    icon: 'none',
    duration: 2 * 1000
  })

  // 2. 判断是否在小程序运行环境
  if (typeof window.__wxjs_environment !== 'undefined' 
      && window.__wxjs_environment === 'miniprogram') {
    setTimeout(() => {
      // 3. 通过 JSSDK 跳转小程序原生登录页
      this.$wx.miniProgram.redirectTo({
        url: `/pages/login/login?isShare=1&back=1&page=${encodeURIComponent(
          '/pages/webH5View/index'
        )}&h5Src=${encodeURIComponent(
          'https://testh5/#/index?isMiniprogram=1'
        )}`
      })
    }, 2000)
  }
}

2.2 关键技术点解析

环境检测

javascript 复制代码
window.__wxjs_environment === 'miniprogram'

这是微信 JSSDK 注入的环境变量,不同于 navigator.userAgent,在 web-view 内是唯一可靠的判断方式。

延迟跳转

javascript 复制代码
setTimeout(() => { ... }, 2000)

给 Toast 足够的展示时间,避免用户感到突兀。

携带 H5 原始地址

javascript 复制代码
h5Src=${encodeURIComponent('H5完整地址')}

h5Src 参数记录 H5 访问地址,登录完成后原路返回。

方法 行为 适用场景
navigateTo 保留当前页面,推入新页面 普通跳转
redirectTo 关闭当前页面,替换为新页面 登录场景,避免返回键回到失效页面

三、登录成功后的回跳链路

3.1 登录页参数接收

javascript 复制代码
// pages/login/login.vue --- onLoad
onLoad(option) {
  this.activityH5 = option.activityH5
  this.goId = option.id
  option.page ? this.goPage = option.page : null
  option.h5Src ? this.h5Src = option.h5Src : null  // 接收 H5 地址

  
  // 处理分享页登录场景
  if (option.isShare && option.isShare === '1') {
    this.isShare = option.isShare
    this.toHideBackButton = true
    // 已登录则跳首页
    if (this.$store.getters.userInfo && this.$store.getters.userInfo.token) {
      uni.switchTab({ url: '/pages/index/index' })
    }
  } else {
    // 根据路由栈长度控制返回按钮
    const pages = getCurrentPages()
    this.toHideBackButton = (pages.length > 1) ? false : true
  }
}

3.2 登录成功回跳逻辑

javascript 复制代码
// pages/login/login.vue --- 登录成功后处理
if (this.h5Src) {
  // H5 场景:登录完成 → 打开 web-view 并带入 H5 地址
  uni.redirectTo({
    url: `${decodeURIComponent(this.goPage)}?webViewUrl=${this.h5Src}`
  })
} else {
  // 普通场景:直接跳转目标页面
  uni.redirectTo({ url: decodeURIComponent(this.goPage) })
}

四、WebView 加载时的 Token 校验

4.1 完整的 onLoad 流程

javascript 复制代码
// pages/webH5View/index.vue --- onLoad
onLoad(option) {
  this.option = option
  this.isShare = option.isShare === '1' ? '1' : '0'

  if (this.userInfo.token) {
    // 有 token → 先校验有效性
    this.isLoginFunction()
  } else {
    // 无 token → 直接打开 H5(H5 侧会触发登录流程)
    this.isLogin = false
    this.handleUrl(this.option)
  }
}

// 是否登陆判断(调用小程序侧接口校验 token)
async isLoginFunction() {
  try {
    const res = await checkMiniProgramLoginFlag()
    this.isLogin = !!res   // token 有效则复用
  } catch (e) {
    this.isLogin = false  // token 失效触发登录
  }
  this.handleUrl(this.option)
}

4.2 URL 参数拼接策略

javascript 复制代码
handleUrl(option) {
  let url = option.webViewUrl 
    ? decodeURIComponent(option.webViewUrl) 
    : decodeURIComponent(option.url || '')
  
  // H5 访问时 token 取空字符串(因为小程序 token 不在 H5 域下)
  let token = this.isLogin ? this.userInfo.token : ''
  
  let params = [
    'token=' + encodeURIComponent(token || ''),
    'userId=' + (this.userInfo.userId || '')
  ]
  
  let baseUrl = url + (url.indexOf('?') !== -1 ? '&' : '?') + params.join('&')
  
  // 标记来源为小程序,供 H5 页面识别并触发登录流程
  if (baseUrl.indexOf('isMiniprogram') === -1) {
    baseUrl = baseUrl + '&isMiniprogram=1'
  }
  
  this.hrefUrl = baseUrl
}

五、登录页返回按钮智能控制

5.1 问题场景

登录页作为入口页面,如果按返回键:

场景 行为 结果
有历史页面 正常返回上一页 ✅ 正常
无历史页面(直接打开小程序) 返回到空页面 ❌ 体验差

5.2 解决方案

javascript 复制代码
// pages/login/login.vue --- onLoad
if (option.isShare && option.isShare === '1') {
  // 分享页进入:强制隐藏返回按钮
  this.toHideBackButton = true
} else {
  // 正常进入:根据路由栈长度智能判断
  const pages = getCurrentPages()
  this.toHideBackButton = (pages.length > 1) ? false : true
}

5.3 导航栏组件

html 复制代码
<!-- topnavigation 接收 isfx prop 控制返回按钮显隐 -->
<topnavigation :isfx="toHideBackButton"></topnavigation>

5.4 逻辑总结

场景 pages.length toHideBackButton 返回按钮
直接打开小程序 → 登录页 1(只有当前页) true 隐藏
从其他页面跳转 → 登录页 ≥ 2 false 显示

六、完整流程图

flowchart TD A[用户在小程序内打开 H5 活动页] --> B{web-view 加载 H5 URL} B --> C{H5 检测 token} C -->|有效| D[H5 正常加载] C -->|失效| E[调用 noValidTokenGoMiniProgramLogin] E --> F{判断环境} F -->|小程序环境| G[2秒后 wx.miniProgram.redirectTo] F -->|非小程序| H[普通 H5 登录流程] G --> I[跳转小程序登录页
携带 isShare/back/h5Src 参数] I --> J{登录页 onLoad} J --> K{isShare=1?} K -->|是| L[强制隐藏返回按钮] K -->|否| M[根据路由栈长度判断] M --> N{pages.length > 1?} N -->|是| O[显示返回按钮] N -->|否| P[隐藏返回按钮] L --> Q[用户完成登录] O --> Q P --> Q Q --> R{有 h5Src?} R -->|是| S[打开 web-view
携带 webViewUrl=h5Src] R -->|否| T[普通跳转目标页面] S --> U[web-view 重新 onLoad] U --> V[checkMiniProgramLoginFlag 校验 token] V -->|有效| W[token 拼接进 URL] V -->|失效| E W --> D

七、踩坑记录

错误做法

javascript 复制代码
wx.miniProgram.navigateTo({
  url: '/pages/login/login?back=1'
})

正确做法

javascript 复制代码
wx.miniProgram.redirectTo({
  url: '/pages/login/login?back=1&h5Src=...'
})

7.2 环境判断不能依赖 UA

错误做法

javascript 复制代码
navigator.userAgent.includes('miniProgram')  // 不可靠

正确做法

javascript 复制代码
window.__wxjs_environment === 'miniprogram'  // JSSDK 注入,可靠

7.3 忘记 encodeURIComponent

错误做法

javascript 复制代码
h5Src=https://example.com/#/page?activityId=123&isMiniprogram=1
// URL 中的 ? 和 & 会与小程序路由参数冲突

正确做法

javascript 复制代码
h5Src=${encodeURIComponent('https://example.com/#/page?activityId=123&isMiniprogram=1')}

八、总结

本文分享了一套小程序 <web-view> 内嵌 H5 的完整会话管理方案,核心要点:

  1. 环境检测 :使用 window.__wxjs_environment 判断运行环境
  2. 跨端跳转 :通过 JSSDK 的 wx.miniProgram.redirectTo 实现 H5 → 小程序原生页面跳转
  3. 参数透传h5Src 参数记录原始 H5 地址,登录成功后原路返回
  4. 智能 UI:根据路由栈长度控制返回按钮显隐,提升用户体验

这套方案已在生产环境验证,希望能帮助到有类似需求的开发者。


参考资料


相关推荐
誰在花里胡哨1 天前
Vue<前端页面装修组件>
前端·vue.js
张元清1 天前
Pareto 动态路由实战:[slug]、catch-all、嵌套布局
前端·javascript·面试
fix一个write十个1 天前
NativeWind v4 与 React Native UI Kit或三方库样式隔离指南
前端·react native
懂懂tty1 天前
React中BeginWork和CompleteWork解析
前端·react.js
_下雨天.1 天前
HAProxy搭建Web群集
前端
梦想CAD控件1 天前
在线CAD开发包图纸转换功能使用指南
前端·javascript·vue.js
亚空间仓鼠1 天前
Ansible之Playbook(三):变量应用
java·前端·ansible
invicinble1 天前
前端技术栈整理
前端
倾颜1 天前
pnpm monorepo 下,如何把 Next.js 应用里的稳定内核拆成内部 workspace 包
前端·react.js·next.js
念格1 天前
Flutter 仿微信输入框最佳实践:自适应高度 + 超行数智能切换全屏
前端·flutter