如何实现UniApp登录拦截?

UniApp 实现登录拦截:CS 小白入门指南

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

对于 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 中初始化(项目入口文件,确保所有页面加载前执行):

javascript 复制代码
 // 路由拦截初始化:拦截所有路由跳转
 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 重启应用),可通过循环简化代码:

typescript 复制代码
 // 需拦截的路由类型数组(覆盖 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(登录页逻辑)
kotlin 复制代码
 // 登录页面:用户输入账号密码后点击登录按钮的逻辑
 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
php 复制代码
 // 退出登录逻辑(如个人中心的退出按钮)
 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 到请求头

javascript 复制代码
 // 请求拦截:自动为接口添加 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. 响应拦截:处理接口错误状态

php 复制代码
 // 响应拦截:处理接口返回的未授权、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 页面生命周期函数,页面加载时触发(仅触发一次)。

  • 适合在页面初始化时执行权限校验;
  • 即使路由拦截失效,页面级校验仍能生效,避免未登录用户停留在权限页面。

页面级校验

javascript 复制代码
 // 需权限的页面(如个人中心 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. 白名单配置(简化拦截逻辑)

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

typescript 复制代码
 // 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. 避免重复跳转登录页(锁机制)

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

ini 复制代码
 // 定义全局锁:标记是否已在跳转登录页
 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

javascript 复制代码
 // 登录成功后存储两个 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>
相关推荐
Change!!1 分钟前
uniapp写的h5,怎么根据页面详情,设置不同的标题
前端·uni-app·标题
浅箬2 分钟前
uniapp 打包之后出现shadow-grey.png去除
前端·uni-app
梵得儿SHI6 分钟前
(第五篇)Spring AI 核心技术攻坚:流式响应与前端集成实现【打字机】效果
前端·webflux·springai·流式响应技术·低延迟ai交互·reactive编程原理·streamapi设计
鹏多多8 分钟前
一文搞懂柯里化:函数式编程技巧的解析和实践案例
前端·javascript·vue.js
前端码农一枚16 分钟前
前端打包性能优化全攻略
前端
Roc.Chang17 分钟前
终极指南:解决 Vue 项目中 “regenerator-runtime/runtime“ 缺失报错
前端·javascript·vue.js·webpack·前端工程
AAA阿giao18 分钟前
从树到楼梯:数据结构与算法的奇妙旅程
前端·javascript·数据结构·学习·算法·力扣·
麦麦大数据21 分钟前
F055 vue+neo4j船舶知识问答系统|知识图谱|问答系统
vue.js·flask·问答系统·知识图谱·neo4j·可视化
BD_Marathon21 分钟前
Vue3组件(SFC)拼接页面
前端·javascript·vue.js