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

0 阅读4分钟

前言

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

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

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

一、问题背景

1.1 会话隔离问题

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

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

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

1.2 期望的用户旅程

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

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

2.1 核心方法

// 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 关键技术点解析

环境检测

window.__wxjs_environment === 'miniprogram'

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

延迟跳转

setTimeout(() => { ... }, 2000)

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

携带 H5 原始地址

h5Src=${encodeURIComponent('H5完整地址')}

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

redirectTo vs navigateTo

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

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

3.1 登录页参数接收

// 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 登录成功回跳逻辑

// 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 流程

// 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 参数拼接策略

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 解决方案

// 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 导航栏组件

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

5.4 逻辑总结

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

六、完整流程图

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[跳转小程序登录页<br/>携带 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<br/>携带 webViewUrl=h5Src]
    R -->|否| T[普通跳转目标页面]
    S --> U[web-view 重新 onLoad]
    U --> V[checkMiniProgramLoginFlag 校验 token]
    V -->|有效| W[token 拼接进 URL]
    V -->|失效| E
    W --> D

七、踩坑记录

7.1 navigateTo 会导致返回键回到失效页面

错误做法

wx.miniProgram.navigateTo({
  url: '/pages/login/login?back=1'
})

正确做法

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

7.2 环境判断不能依赖 UA

错误做法

navigator.userAgent.includes('miniProgram')  // 不可靠

正确做法

window.__wxjs_environment === 'miniprogram'  // JSSDK 注入,可靠

7.3 忘记 encodeURIComponent

错误做法

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

正确做法

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:根据路由栈长度控制返回按钮显隐,提升用户体验

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


参考资料