如何实现UniApp登录拦截?

如何实现UniApp登录拦截?

登录拦截是前端权限控制的基础功能,核心目的是:未登录用户无法访问需要权限的页面(如个人中心、订单列表),同时拦截未授权的接口请求,避免敏感数据泄露或无效操作。

对于 CS 小白而言,UniApp 登录拦截的核心逻辑可概括为「状态校验 + 行为拦截」:通过 token 标识登录状态,拦截非法的页面跳转和接口请求,引导未登录用户前往登录页。

前置知识:什么是 Token?

token 是用户登录成功后,后端返回的「身份凭证」,相当于登录后的「电子门票」。

后续访问需要权限的页面或接口时,前端需携带 token 证明「已登录」身份,后端验证 token 有效则允许访问,无效则拒绝。

token 需持久化存储(如本地存储),否则 App 重启或页面刷新后会丢失,导致用户需重新登录。

核心实现一:路由拦截(控制页面跳转)

UniApp 中实现路由拦截的核心 API 是 uni.addInterceptor,专门用于拦截路由跳转行为(如 uni.navigateTouni.redirectTo 等)。

关键 API 解释

uni.addInterceptor:UniApp 内置拦截器 API,支持拦截路由、请求、上传 / 下载等行为。

  • 通过 invoke 回调在拦截行为执行前触发自定义逻辑(重点掌握);

  • 通过 success 回调在拦截行为成功后处理结果;

  • 小白只需关注 invoke 回调即可实现基础拦截。

基础路由拦截

路由拦截需在全局生效,建议在 main.js 中初始化(项目入口文件,确保所有页面加载前执行):

ts 复制代码
// 路由拦截初始化:拦截所有路由跳转
uni.addInterceptor('navigateTo', {
  invoke(options) {
    // 1. 获取本地存储的 token(后续讲解如何存储)
    const token = uni.getStorageSync('userToken');
    // 2. 判断 token 是否存在:不存在则拦截跳转,跳转至登录页
    if (!token) {
      // 使用 redirectTo 跳转,避免用户通过返回键回到原页面
      uni.redirectTo({ url: '/pages/login/login' });
      // 阻止原跳转行为(关键!否则会同时跳转到目标页和登录页)
      return false;
    }
    // 3. token 存在:允许跳转(无需额外操作,默认继续执行原跳转)
  }
});

扩展:拦截所有路由类型

上述代码仅拦截 navigateTo 跳转,实际开发中需覆盖所有路由方式(如 switchTab 切换底部标签、reLaunch 重启应用),可通过循环简化代码:

ts 复制代码
// 需拦截的路由类型数组(覆盖 UniApp 所有路由 API)
const routeTypes = ['navigateTo', 'redirectTo', 'switchTab', 'reLaunch', 'navigateBack'];

// 循环为所有路由类型添加拦截器
routeTypes.forEach(type => {
  uni.addInterceptor(type, {
    invoke(options) {
      const token = uni.getStorageSync('userToken');
      // 关键:排除登录页本身,避免死循环
      if (!token && !options.url.includes('/pages/login/login')) {
        uni.redirectTo({ url: '/pages/login/login' });
        return false;
      }
    }
  });
});

避坑提醒 🚨

必须排除登录页:如果不排除,未登录时跳转登录页会被自身拦截,陷入「跳转 - 拦截 - 再跳转」的死循环,这是小白最容易踩的坑。

核心实现二:本地存储 Token(持久化登录状态)

登录成功后,需将后端返回的 token 存入本地,以便路由拦截、请求拦截时获取,核心 API 是 uni.setStorageSyncuni.getStorageSync

关键 API 解释

uni.setStorageSync(key, value):UniApp 同步本地存储 API。

  • 以键值对形式存入本地(类似浏览器的 localStorage);

  • 同步执行:代码会等待存储完成后再继续,无需处理异步回调,小白友好;

  • 适合存储简单数据(如 token、用户昵称、ID)。

uni.getStorageSync(key):同步读取本地存储数据。

  • 根据 key 读取对应 value,无数据则返回 nullundefined

  • 读取速度快,适合频繁获取的场景(如拦截器中获取 token)。

uni.removeStorageSync(key):同步删除本地存储的指定 key 数据,用于退出登录时清除 token。

Token 存储与删除

1. 登录成功后存储 Token(登录页逻辑)
ts 复制代码
// 登录页面:用户输入账号密码后点击登录按钮的逻辑
async function login() {
  // 1. 获取用户输入的账号密码(假设已通过表单绑定)
  const { username, password } = this.userForm;
  
  // 2. 调用后端登录接口(后续请求拦截会补充接口逻辑)
  const res = await uni.request({
    url: 'https://api.example.com/login', // 后端登录接口地址
    method: 'POST',
    data: { username, password } // 传递账号密码
  });
  
  // 3. 处理后端返回结果(假设后端约定:code=200 为成功)
  if (res.data.code === 200) {
    // 4. 存储 token 到本地,key 建议语义化(如 'userToken')
    uni.setStorageSync('userToken', res.data.data.token);
    
    // 5. 存储成功后跳转至首页或目标页面
    uni.redirectTo({ url: '/pages/index/index' });
  } else {
    // 登录失败:提示用户(UniApp 内置提示 API)
    uni.showToast({ title: res.data.msg || '登录失败', icon: 'none' });
  }
}
2. 退出登录时删除 Token
ts 复制代码
// 退出登录逻辑(如个人中心的退出按钮)
function logout() {
  // 1. 清除本地存储的 token
  uni.removeStorageSync('userToken');
  
  // 2. 跳转至登录页,关闭所有页面避免返回
  uni.reLaunch({ url: '/pages/login/login' });
}

注意事项 📌

本地存储的 token 是明文存储(如 H5 端可通过浏览器开发者工具查看),切勿存储密码等敏感信息,仅存储 token 等非敏感凭证。

核心实现三:请求拦截与响应拦截(控制接口访问)

路由拦截能阻止未登录用户访问页面,但无法阻止用户通过抓包工具或直接调用接口发起请求,因此需要「请求拦截 + 响应拦截」形成双重保障。

核心逻辑

  • 请求拦截:为所有需要权限的接口自动添加 token 到请求头,让后端验证身份;

  • 响应拦截:处理接口返回的错误状态(如 token 过期、未授权),强制跳转登录页。

关键 API 解释

uni.addInterceptor('request'):拦截所有通过 uni.request 发起的接口请求。

  • invoke 回调:请求发送前执行(用于添加请求头 token);

  • success 回调:请求响应后执行(用于处理 401 等错误状态);

  • fail 回调:请求失败时执行(如网络错误)。

1. 请求拦截:添加 Token 到请求头

ts 复制代码
// 请求拦截:自动为接口添加 token
uni.addInterceptor('request', {
  invoke(options) {
    // 1. 获取本地存储的 token
    const token = uni.getStorageSync('userToken');
    
    // 2. 如果 token 存在,添加到请求头(需与后端约定字段名)
    if (token) {
      // 初始化请求头(避免 options.header 为 undefined)
      options.header = options.header || {};
      // 常见格式:Bearer + 空格 + token(后端约定为准,也可能直接传 token)
      options.header.Authorization = `Bearer ${token}`;
    }
    
    // 3. 无需阻止请求,仅添加配置,默认继续发送
  }
});

2. 响应拦截:处理接口错误状态

ts 复制代码
// 响应拦截:处理接口返回的未授权、token 过期等状态
uni.addInterceptor('request', {
  success(res) {
    // 1. 假设后端约定状态码:401=未授权(token 无效/过期),403=权限不足
    const { code, msg } = res.data;
    
    // 2. 处理 401 未授权:清除无效 token,跳转登录页
    if (code === 401) {
      // 清除本地无效 token,避免下次请求仍携带
      uni.removeStorageSync('userToken');
      
      // 提示用户登录过期
      uni.showToast({ title: msg || '登录已过期,请重新登录', icon: 'none' });
      
      // 跳转登录页(关闭所有页面,避免返回)
      uni.reLaunch({ url: '/pages/login/login' });
    }
    
    // 3. 处理 403 权限不足:仅提示,不跳转
    if (code === 403) {
      uni.showToast({ title: msg || '暂无权限访问', icon: 'none' });
    }
  },
  fail(err) {
    // 处理网络错误等请求失败场景
    uni.showToast({ title: '网络错误,请稍后重试', icon: 'none' });
  }
});

避坑提醒 📌

请求头的 token 字段名需与后端严格约定:大多数后端使用 Authorization 作为字段名,格式为 Bearer + 空格 + token;若后端要求直接传 token(如 header: { token: 'xxx' }),需按后端规则修改,否则后端无法识别。

核心实现四:页面级权限校验(兜底方案)

路由拦截和请求拦截已能覆盖大部分场景,但存在特殊情况(如 H5 端页面刷新后路由拦截未触发),因此需要在页面加载时添加「兜底校验」。

关键生命周期解释

onLoad:UniApp 页面生命周期函数,页面加载时触发(仅触发一次)。

  • 适合在页面初始化时执行权限校验;

  • 即使路由拦截失效,页面级校验仍能生效,避免未登录用户停留在权限页面。

页面级校验

ts 复制代码
// 需权限的页面(如个人中心 pages/mine/mine.vue)
export default {
  // 页面加载时执行校验
  onLoad() {
    // 1. 获取本地 token
    const token = uni.getStorageSync('userToken');
    
    // 2. 无 token 则跳转登录页,关闭当前页面
    if (!token) {
      uni.redirectTo({ url: '/pages/login/login' });
    }
  }
};

适用场景 🚀

  • H5 端:用户按 F5 刷新页面后,路由拦截可能未重新执行,onLoad 校验会生效;

  • 小程序端:页面被分享后,用户打开时可能跳过路由拦截,需通过页面级校验兜底。

进阶优化:小白也能掌握的实用技巧

基础登录拦截实现后,可通过以下优化让功能更稳定,应对实际开发中的复杂场景。

1. 白名单配置(简化拦截逻辑)

部分页面 / 接口无需登录即可访问(如首页、注册页、登录接口本身),可通过「白名单」统一管理,避免在拦截器中写大量判断。

ts 复制代码
// 1. 页面白名单:无需登录即可访问的页面路径
const pageWhiteList = [
  '/pages/login/login', // 登录页
  '/pages/register/register', // 注册页
  '/pages/index/index' // 首页
];

// 2. 接口白名单:无需 token 即可访问的接口地址
const apiWhiteList = [
  '/login', // 登录接口
  '/register', // 注册接口
  '/home/banner' // 首页轮播图接口
];

// 3. 路由拦截中使用页面白名单
routeTypes.forEach(type => {
  uni.addInterceptor(type, {
    invoke(options) {
      const token = uni.getStorageSync('userToken');
      // 校验逻辑:不在白名单 + 无 token → 跳转登录页
      if (!pageWhiteList.includes(options.url) && !token) {
        uni.redirectTo({ url: '/pages/login/login' });
        return false;
      }
    }
  });
});

// 4. 请求拦截中使用接口白名单
uni.addInterceptor('request', {
  invoke(options) {
    const token = uni.getStorageSync('userToken');
    // 校验逻辑:不在白名单 + 有 token → 添加 token 到请求头
    if (!apiWhiteList.some(api => options.url.includes(api)) && token) {
      options.header = options.header || {};
      options.header.Authorization = `Bearer ${token}`;
    }
  }
});

2. 避免重复跳转登录页(锁机制)

当用户快速点击多个需要权限的按钮时,可能触发多次路由拦截,导致多次跳转登录页,需通过「锁机制」避免。

ts 复制代码
// 定义全局锁:标记是否已在跳转登录页
let isRedirecting = false;

// 路由拦截中添加锁逻辑
routeTypes.forEach(type => {
  uni.addInterceptor(type, {
    invoke(options) {
      const token = uni.getStorageSync('userToken');
      if (!token && !options.url.includes('/pages/login/login')) {
        // 未在跳转中才执行跳转
        if (!isRedirecting) {
          isRedirecting = true; // 上锁
          uni.redirectTo({
            url: '/pages/login/login',
            success() {
              isRedirecting = false; // 跳转成功后解锁
            }
          });
        }
        return false;
      }
    }
  });
});

3. Token 过期处理(刷新 Token)

token 有有效期(如 2 小时),过期后需重新登录,影响用户体验,可通过「刷新 token」优化:后端返回两个 token(短期访问令牌 accessToken + 长期刷新令牌 refreshToken),到期前自动用 refreshToken 换取新的 accessToken

ts 复制代码
// 登录成功后存储两个 token
if (res.data.code === 200) {
  const { accessToken, refreshToken, expiresIn } = res.data.data;
  uni.setStorageSync('accessToken', accessToken); // 短期访问令牌(2小时)
  uni.setStorageSync('refreshToken', refreshToken); // 长期刷新令牌(7天)
  // 存储过期时间(当前时间 + 有效期毫秒数)
  uni.setStorageSync('tokenExpireTime', Date.now() + expiresIn * 1000);
}

// 请求拦截中添加刷新 token 逻辑
uni.addInterceptor('request', {
  async invoke(options) {
    const accessToken = uni.getStorageSync('accessToken');
    const refreshToken = uni.getStorageSync('refreshToken');
    const expireTime = uni.getStorageSync('tokenExpireTime');
    
    // 跳过白名单接口
    if (apiWhiteList.some(api => options.url.includes(api))) return;
    
    // token 即将过期(剩余 5 分钟)且有刷新令牌,自动刷新
    if (accessToken && expireTime - Date.now() < 5 * 60 * 1000 && refreshToken) {
      const refreshRes = await uni.request({
        url: 'https://api.example.com/refreshToken', // 后端刷新接口
        method: 'POST',
        data: { refreshToken }
      });
      
      if (refreshRes.data.code === 200) {
        // 刷新成功:更新本地 token 和过期时间
        uni.setStorageSync('accessToken',</doubaocanvas>
相关推荐
一 乐几秒前
酒店预约|基于springboot + vue酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端
行走的陀螺仪17 分钟前
Sass 详细指南
前端·css·rust·sass
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ22 分钟前
React 怎么区分导入的是组件还是函数,或者是对象
前端·react.js·前端框架
LYFlied35 分钟前
【每日算法】LeetCode 136. 只出现一次的数字
前端·算法·leetcode·面试·职场和发展
子春一237 分钟前
Flutter 2025 国际化与本地化工程体系:从多语言支持到文化适配,打造真正全球化的应用
前端·flutter
前端无涯38 分钟前
React/Vue 代理配置全攻略:Vite 与 Webpack 实战指南
vue.js·react.js
QT 小鲜肉1 小时前
【Linux命令大全】001.文件管理之file命令(实操篇)
linux·运维·前端·网络·chrome·笔记
羽沢312 小时前
ECharts 学习
前端·学习·echarts
LYFlied2 小时前
WebAssembly (Wasm) 跨端方案深度解析
前端·职场和发展·wasm·跨端