Vue+Laravel登录后路由守卫校验token返回401问题
Vue Router 全局守卫首次登录跳转401问题排查
问题背景
使用VueJS、Vue Router搭配后端Laravel开发小型项目时,多次配置导航守卫未达预期:
- 登录组件本身运行正常:调用
http://proudata.test/api/login接口,返回isLogged为true时,将响应数据存入localStorage的token字段,再通过router.push跳转至dashboard路由 - 异常表现:登录成功后首次跳转触发token校验接口时直接返回401未授权,请求未携带存入localStorage的Bearer Token;浏览器开发者工具确认localStorage已正确写入token,手动刷新页面后所有请求可正常携带token,功能恢复正常
- 预期逻辑:用户每次跳转页面前,调用
validate-token接口校验token有效性、是否过期,未通过校验则跳转回登录页
现有实现代码
登录组件方法(Login.vue)
login() { axios.post('http://proudata.test/api/login', this.user).then(response => { if (response.data.data.isLogged == true) { localStorage.setItem('token', JSON.stringify(response.data)); router.push({ name: 'dashboard' }); } }).catch(error => { this.error = true; this.message = error.response.data.message }); }
全局前置路由守卫(routes.js)
router.beforeEach(async (routeTo, routeFrom, next) => { const authRequired = routeTo.matched.some((route) => route.meta.authRequired) if (!authRequired) return next() // 首次登录跳转时此处请求返回401 await client.get('validate-token').then(() => { next(); }).catch(() => { next({ name: 'login' }) }); });
封装的Axios公共请求客户端(Client.js)
import axios from 'axios'; let userData = localStorage.getItem('token'); let authenticated = {data:{}}; if(userData !== null){ authenticated = JSON.parse(userData); } const client = axios.create({ baseURL: ((authenticated.data.domain !== null) ? authenticated.data.domain : 'http://domain.test/api') }); client.interceptors.request.use(config => { if(userData !== null){ config.headers.Authorization = authenticated.data.token ? `Bearer ${authenticated.data.token}` : null; } return config; }); export default client;
登录接口成功响应结构
{ "data":{ "isLogged":true, "token": "gkljdfslkgjdfklgjfdlkgj", "domain": "http://user.multitenancy.test" } }
根本原因
问题出在Axios客户端的初始化逻辑:
userData、authenticated两个变量是在模块首次被import加载时执行赋值的,仅会读取一次当时localStorage中的值。页面首次加载时用户未登录,localStorage中无token,这两个变量会被初始化为null和空对象- 登录成功后虽然新的token被写入localStorage,但模块顶层的变量不会自动更新,请求拦截器中读取的始终是模块初始化时的旧空值,因此不会给请求添加Authorization头
- 手动刷新页面后,整个JS资源重新加载,模块初始化时localStorage已经存在有效token,因此变量能拿到正确值,请求可以正常携带头信息
- 额外问题:
baseURL同样是模块初始化时固定的,首次登录返回的多租户域名不会被应用到后续请求上,刷新后才会生效
将token校验逻辑放在beforeEach全局守卫中是合理的全局鉴权实现方式,本身没有问题。
修复方案
将localStorage读取、token和baseURL的赋值逻辑全部移到请求拦截器内部,保证每次发起请求时都读取最新的本地存储数据,不要在模块顶层做一次性初始化:
import axios from 'axios'; const client = axios.create({ // 配置默认baseURL兜底 baseURL: 'http://domain.test/api' }); client.interceptors.request.use(config => { // 每次发请求前实时读取最新的本地存储数据 const userData = localStorage.getItem('token'); if (userData !== null) { const authenticated = JSON.parse(userData); // 动态适配多租户域名 if (authenticated.data?.domain) { config.baseURL = authenticated.data.domain; } // 动态添加鉴权头 if (authenticated.data?.token) { config.headers.Authorization = `Bearer ${authenticated.data.token}`; } } return config; }); export default client;
可选优化:路由守卫中混用async/await和then/catch的写法可以简化,逻辑保持一致即可:
router.beforeEach(async (routeTo, routeFrom, next) => { const authRequired = routeTo.matched.some((route) => route.meta.authRequired) if (!authRequired) return next() try { await client.get('validate-token') next() } catch { next({ name: 'login' }) } });
内容的提问来源于stack exchange,提问作者user19252998
相关产品推荐
相关产品推荐

