如何实现UniApp登录拦截?
登录拦截是前端权限控制的基础功能,核心目的是:未登录用户无法访问需要权限的页面(如个人中心、订单列表),同时拦截未授权的接口请求,避免敏感数据泄露或无效操作。
对于 CS 小白而言,UniApp 登录拦截的核心逻辑可概括为「状态校验 + 行为拦截」:通过 token 标识登录状态,拦截非法的页面跳转和接口请求,引导未登录用户前往登录页。
前置知识:什么是 Token?
token 是用户登录成功后,后端返回的「身份凭证」,相当于登录后的「电子门票」。
后续访问需要权限的页面或接口时,前端需携带 token 证明「已登录」身份,后端验证 token 有效则允许访问,无效则拒绝。
token 需持久化存储(如本地存储),否则 App 重启或页面刷新后会丢失,导致用户需重新登录。
核心实现一:路由拦截(控制页面跳转)
UniApp 中实现路由拦截的核心 API 是 uni.addInterceptor,专门用于拦截路由跳转行为(如 uni.navigateTo、uni.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.setStorageSync 和 uni.getStorageSync。
关键 API 解释
uni.setStorageSync(key, value):UniApp 同步本地存储 API。
-
以键值对形式存入本地(类似浏览器的
localStorage); -
同步执行:代码会等待存储完成后再继续,无需处理异步回调,小白友好;
-
适合存储简单数据(如 token、用户昵称、ID)。
uni.getStorageSync(key):同步读取本地存储数据。
-
根据 key 读取对应 value,无数据则返回
null或undefined; -
读取速度快,适合频繁获取的场景(如拦截器中获取 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>