logout必须同步清空所有存储层,包括后端接口、localstorage、sessionstorage、vuex、cookie及内存变量,并主动校验token时效、服务端判定离线、确保状态归零后再跳转。
logout 函数必须同步清空所有存储层
前端登出不是点个按钮就完事,logout 里漏掉任意一层缓存,都可能让下一次登录复用旧状态。常见错误是只清 localstorage 却忘了 sessionstorage、vuex、cookie 或内存变量。
实操建议按顺序执行:
- 调用后端
/api/logout接口(带认证头),并确保 await 等待成功响应(失败也要继续清理) -
localStorage.removeItem('token')、localStorage.removeItem('userInfo')、localStorage.removeItem('timeStamp')、localStorage.removeItem('lastRecordTime') -
sessionStorage.clear()(别只删 key,有些权限临时态存在 sessionStorage) - 触发 Vuex mutation:
store.commit('SET_TOKEN', '')和store.commit('SET_USERINFO', {}) - 清除 Cookie:
document.cookie.split(';').forEach(c => document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date(0).toUTCString()};path=/`))
被动退出时 token 失效判断不能只靠后端 401
等接口返回 401 Unauthorized 再登出,用户可能已误操作多次,且部分请求(如 WebSocket、定时任务)根本不会走 HTTP 拦截器。纯依赖后端响应做登出,等于把安全控制权交出去。
必须在前端加主动时效校验:
- 登录成功后立即写入
timeStamp(毫秒时间戳),用setItem('timeStamp', Date.now()) - 每次发请求前调用
isCheckTimeout(),逻辑为:Date.now() - getItem('timeStamp') > TOKEN_TIMEOUT_VALUE(例如 2 小时 =7200000) - 一旦超时,不发新请求,直接触发完整
logout流程 - 注意:该判断要放在请求拦截器最顶部,早于 header 添加和 token 注入
浏览器关闭/刷新场景下 token 清理不可靠,得换思路
beforeunload 和 unload 事件里发登出请求基本无效——浏览器关闭时异步请求大概率被中断,fetch + keepalive: true 在 Safari 和部分安卓 WebView 里也不稳定。
更可行的方案是“服务端主动判定”:
- 前端每秒写
localStorage.setItem('lastRecordTime', Date.now())(登录后启动setInterval) - 所有请求 header 带上
lastRecordTime和nowTime - 后端收到后计算差值,若
nowTime - lastRecordTime > 5000,视为异常离线,立即清 Redis 中对应token - 前端配合:在响应拦截器中监听
401或后端自定义状态码(如499表示“会话已因长时间无操作被回收”),再执行本地清理
登出后 router 跳转前必须确保状态已归零
很多项目登出后跳到 /login,但页面一闪而过又闪回首页——是因为 router.push 执行太快,Vuex 或组件内部 still 持有旧 token,触发守卫重定向或接口自动重试。
关键动作顺序不能错:
- 先 commit 清空 Vuex 中所有敏感 state(不只是 token,还有
roles、permissions、menuList) - 再清所有 storage 层(
localStorage/sessionStorage/ Cookie) - 最后才
router.push('/login');如果用了v-if="isRouterAlive"刷新机制,要在跳转前设isRouterAlive = false,再$nextTick恢复 - 避免使用
window.location.reload(),它会白屏且可能残留未销毁的定时器或 WebSocket 连接
真正麻烦的从来不是“怎么登出”,而是“怎么确认登干净了”。token、时间戳、心跳标记、内存引用、路由守卫、权限指令……任何一层没断干净,都可能让登出变成假退出。










