uni-app 中如何实现登录鉴权与 token 无感刷新?

在 uni-app 面试中,登录鉴权与 token 无感刷新是一道高频且能区分候选人工程化能力的题目。它考察的不仅是对 API 的熟悉程度,更是对前端安全、请求拦截、并发处理以及用户体验的综合把控。本文将从面试回答的角度,系统拆解 uni-app 中实现登录鉴权与无感刷新的完整方案。

一、面试官想听到什么?

当面试官抛出这个问题时,他通常期待你从以下几个层面展开:

  1. 登录鉴权的整体流程:从用户输入账号密码到获取 token,再到后续请求携带 token。
  2. token 的存储与使用:存在哪里?如何附加到请求上?
  3. 无感刷新的触发时机与实现:什么时候刷新?如何保证用户无感知?
  4. 并发请求的处理:多个请求同时发现 token 过期怎么办?
  5. 边界情况与安全性:刷新失败怎么办?如何防止死循环?

下面我们逐一拆解。

二、登录鉴权的基本流程

1. 登录与 token 获取

用户提交账号密码后,前端调用登录接口,服务端返回 access_tokenrefresh_token。通常 access_token 有效期较短(如 2 小时),refresh_token 有效期较长(如 7 天)。

// 登录请求示例
const res = await uni.request({
  url: '/api/login',
  method: 'POST',
  data: { username, password }
})
const { accessToken, refreshToken } = res.data
uni.setStorageSync('access_token', accessToken)
uni.setStorageSync('refresh_token', refreshToken)

2. 请求拦截:自动携带 token

在 uni-app 中,推荐对 uni.request 进行二次封装,利用拦截器统一附加 token。

const request = (options) => {
  return new Promise((resolve, reject) => {
    const token = uni.getStorageSync('access_token')
    uni.request({
      ...options,
      header: {
        ...options.header,
        Authorization: token ? `Bearer ${token}` : ''
      },
      success: (res) => {
        // 处理响应
        if (res.statusCode === 401) {
          // token 过期,触发刷新逻辑
          handleTokenExpired(options, resolve, reject)
        } else {
          resolve(res.data)
        }
      },
      fail: reject
    })
  })
}

3. 路由拦截:未登录跳转登录页

除了请求拦截,还需要在页面跳转时校验登录态。可以在 App.vueonLaunch 或使用 uni.addInterceptornavigateToswitchTab 等路由方法进行拦截。

// 路由拦截示例
const whiteList = ['/pages/login/index', '/pages/register/index']
uni.addInterceptor('navigateTo', {
  invoke(args) {
    const token = uni.getStorageSync('access_token')
    const path = args.url.split('?')[0]
    if (!token && !whiteList.includes(path)) {
      uni.redirectTo({ url: '/pages/login/index' })
      return false
    }
    return true
  }
})

三、Token 无感刷新的核心实现

无感刷新的关键在于:当 access_token 过期时,前端自动使用 refresh_token 换取新的 access_token,并重新发起之前失败的请求,用户全程无感知。

1. 基本思路

  • 响应拦截中判断状态码为 401(或业务码表示 token 过期)。
  • 调用刷新接口,用 refresh_token 换取新的 access_token
  • 刷新成功后,更新本地存储,并重试之前失败的请求。
  • 刷新失败(如 refresh_token 也过期),则清空登录态,跳转登录页。

2. 并发请求的难题

如果同时有 5 个请求都返回 401,不能发起 5 次刷新请求。需要引入一个“刷新中”的标志位,让后续请求等待刷新完成后再重试。

let isRefreshing = false
let requestQueue = []

const handleTokenExpired = (options, resolve, reject) => {
  if (isRefreshing) {
    // 正在刷新,将请求加入队列
    requestQueue.push({ options, resolve, reject })
    return
  }
  isRefreshing = true
  const refreshToken = uni.getStorageSync('refresh_token')
  
  uni.request({
    url: '/api/refresh',
    method: 'POST',
    data: { refreshToken },
    success: (res) => {
      if (res.statusCode === 200 && res.data.accessToken) {
        const newToken = res.data.accessToken
        uni.setStorageSync('access_token', newToken)
        // 重试当前请求
        retryRequest(options).then(resolve).catch(reject)
        // 重试队列中的请求
        requestQueue.forEach(({ options, resolve, reject }) => {
          retryRequest(options).then(resolve).catch(reject)
        })
      } else {
        // 刷新失败,清空登录态
        clearAuth()
        uni.redirectTo({ url: '/pages/login/index' })
        requestQueue.forEach(({ reject }) => reject(new Error('登录已过期')))
      }
    },
    fail: () => {
      clearAuth()
      uni.redirectTo({ url: '/pages/login/index' })
    },
    complete: () => {
      isRefreshing = false
      requestQueue = []
    }
  })
}

3. 重试请求的封装

retryRequest 需要重新执行原始请求,并携带新的 token。

const retryRequest = (options) => {
  return new Promise((resolve, reject) => {
    const token = uni.getStorageSync('access_token')
    uni.request({
      ...options,
      header: {
        ...options.header,
        Authorization: `Bearer ${token}`
      },
      success: (res) => resolve(res.data),
      fail: reject
    })
  })
}

四、面试加分项:边界情况与优化

1. 防止刷新死循环

如果刷新接口本身也返回 401,不能再触发刷新逻辑。可以在 handleTokenExpired 中判断当前请求的 URL 是否为刷新接口,或者设置一个最大重试次数。

2. 刷新接口的并发控制

除了用 isRefreshing 标志位,还可以使用 Promise 缓存:当刷新请求进行中时,返回同一个 Promise 给所有等待者。

let refreshPromise = null

const refreshToken = () => {
  if (!refreshPromise) {
    refreshPromise = doRefresh().finally(() => {
      refreshPromise = null
    })
  }
  return refreshPromise
}

3. 主动刷新与被动刷新结合

除了被动等待 401,还可以在 access_token 即将过期前主动刷新。例如在 App.vueonShow 中检查 token 剩余有效期,若小于 5 分钟则提前刷新,进一步降低用户感知。

4. 安全性考虑

  • refresh_token 应存储在更安全的位置,避免 XSS 攻击。在 uni-app 中,uni.setStorageSync 是明文存储,敏感场景可考虑加密后再存储。
  • 刷新接口应使用 HTTPS,并设置合理的过期时间。
  • 退出登录时务必清空本地 token,并通知服务端使 refresh_token 失效。

五、总结

在 uni-app 中实现登录鉴权与 token 无感刷新,核心是三点:

  1. 封装请求:统一附加 token,统一处理 401。
  2. 队列与标志位:解决并发刷新问题,避免重复刷新。
  3. 重试机制:刷新成功后自动重试失败请求,实现用户无感知。

面试回答时,建议先讲整体流程,再深入并发处理和边界情况,最后补充主动刷新、安全性等加分点。这样既能体现你的工程化思维,也能展示对细节的把控,给面试官留下深刻印象。

未经允许不得转载:任鹏个人博客 » uni-app 中如何实现登录鉴权与 token 无感刷新?

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏