三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

基于 Vue3 实现用户闲置自动退出登录方案

基于 Vue3 实现用户闲置自动退出登录方案

一、方案背景

在后台管理系统、企业级中台等权限管控严格的场景中,用户长时间未操作设备,易造成账号未锁屏、信息泄露、越权访问等安全风险。为满足系统安全规范,需实现用户闲置超时自动登出能力:当用户连续无操作达到指定时长,系统自动清空登录态、跳转至登录页,杜绝未授权访问隐患。

本文基于Vue3 Composition API封装通用、可复用的useIdleLogout组合式函数,实现高适配、高稳定性的闲置监控能力,并完成全局优雅集成,同时解决跨标签页同步、页面刷新恢复、动态配置超时时间等核心痛点。

二、需求分析

结合业务安全要求与前端交互场景,闲置自动登出方案需满足以下核心需求:

  1. 精准定义用户闲置状态:监听键盘输入、鼠标点击、页面滚动、移动端触摸等用户主动操作,以此判定用户是否处于活跃状态。

  2. 超时时间动态可配置:闲置超时时长由后端接口下发用户配置,前端不硬编码,支持不同用户、不同环境灵活适配。

  3. 跨标签页/窗口状态同步:用户多标签页同时登录系统时,所有页面共享用户活跃状态,一处操作全局更新,一处超时全局登出。

  4. 登录状态差异化管控:未登录状态下禁用闲置监控,仅用户登录成功后自动启动监控逻辑。

  5. 页面刷新状态恢复:页面刷新后不重置计时,自动读取历史活跃时间,若刷新前已超时则直接登出,规避安全漏洞。

  6. 性能与稳定性保障:避免事件重复绑定、内存泄漏、重复登出等问题,适配长期运行的后台系统。

三、核心设计思路

3.1 状态分层设计(事件绑定 + 逻辑启用分离)

将监控能力拆分为「事件预绑定」和「校验逻辑启用」两个阶段,解耦初始化与业务逻辑:

  • start():全局预绑定用户操作 DOM 事件,仅监听不校验,避免后续重复绑定事件。

  • enable():登录成功后触发,正式启用闲置超时校验逻辑,初始化活跃时间、校验当前是否已超时。

该设计可实现应用启动时提前准备监听能力,登录后快速激活校验,时序更合理、交互更稳定。

3.2 跨页面状态同步方案

借助localStorage持久化存储用户最后活跃时间戳,实现多标签页、多窗口数据共享。所有页面读取同一份时间戳,任意页面的用户操作会全局更新时间戳,超时后清空存储,实现全局同步登出。

3.3 刷新场景安全兜底

页面刷新后,系统会优先读取本地存储的最后活跃时间,结合后端下发的超时配置二次校验,若已超出闲置时长则直接执行登出逻辑,避免刷新重置计时导致的安全隐患。

四、核心代码实现

4.1 封装闲置监控 Composable

新建composables/useIdleLogout.js,封装所有闲置校验、状态管理、登出逻辑,实现能力复用。

import { ref } from 'vue' import useUserStore from '@/store/modules/user' // 本地存储key:最后用户活跃时间戳 export const STORAGE_KEY = 'LAST_ACTIVITY_TIMESTAMP' // 需要监听的用户活跃事件 const ACTIVITY_EVENTS = ['keydown', 'click', 'scroll', 'touchstart'] /** * 获取最后一次用户活跃时间戳 * @returns {number} 时间戳 */ function getLastActivityTime() { const ts = localStorage.getItem(STORAGE_KEY) return ts ? parseInt(ts, 10) : 0 } /** * 更新用户最后活跃时间戳 */ function updateLastActivityTime() { localStorage.setItem(STORAGE_KEY, Date.now().toString()) } /** * 校验是否闲置超时 * @param {number} idleTimeMs - 超时毫秒数 * @returns {boolean} 是否超时 */ function isTimeout(idleTimeMs) { if (!idleTimeMs || idleTimeMs <= 0) return false const lastActiveTime = getLastActivityTime() // 无活跃记录默认不超时 if (lastActiveTime === 0) return false return Date.now() - lastActiveTime > idleTimeMs } /** * 清空登录态并跳转登录页(全局通用方法) */ function clearBrandPrefixAndReloadToLogin() { // 清空路由前缀、缓存、登录标识等全局状态 // 根据项目实际业务补充逻辑 window.location.href = '/login' } export function useIdleLogout() { const userStore = useUserStore() let isLoggingOut = false // 登出锁:防止重复登出 let hasBoundEvents = false // 事件绑定锁:防止重复绑定 const isEnabled = ref(false) // 监控是否启用 /** * 获取后端配置的闲置超时时间(转换为毫秒) * @returns {number} 超时毫秒数 */ const getIdleTimeMs = () => { const idleMinutes = Number(userStore?.user?.idleLogoutMinutes || 0) return idleMinutes > 0 ? idleMinutes * 60 * 1000 : 0 } /** * 执行自动登出逻辑 */ const logout = async () => { if (isLoggingOut) return isLoggingOut = true // 执行全局登出清空逻辑 await userStore.logOut() // 清除活跃时间缓存 localStorage.removeItem(STORAGE_KEY) // 跳转登录页 clearBrandPrefixAndReloadToLogin() // 解锁登出状态 isLoggingOut = false } /** * 用户活跃事件回调 */ const onUserActivity = () => { // 监控未启用,不执行任何逻辑 if (!isEnabled.value) return const idleTimeMs = getIdleTimeMs() if (!idleTimeMs) return // 已超时则登出,否则更新活跃时间 if (isTimeout(idleTimeMs)) { logout() } else { updateLastActivityTime() } } /** * 绑定用户活跃监听事件 */ const bindEvents = () => { ACTIVITY_EVENTS.forEach(event => window.addEventListener(event, onUserActivity)) } /** * 解绑用户活跃监听事件 */ const unbindEvents = () => { ACTIVITY_EVENTS.forEach(event => window.removeEventListener(event, onUserActivity)) } /** * 启动监控:仅绑定事件,不启用校验逻辑 */ const start = () => { if (hasBoundEvents) return bindEvents() hasBoundEvents = true } /** * 停止监控:解绑事件、关闭校验 */ const stop = () => { isEnabled.value = false if (!hasBoundEvents) return unbindEvents() hasBoundEvents = false } /** * 启用闲置超时校验(登录成功后调用) */ const enable = () => { if (isEnabled.value) return const idleTimeMs = getIdleTimeMs() // 后端未配置超时时间,不启用监控 if (!idleTimeMs) return // 页面刷新后校验是否已超时 if (isTimeout(idleTimeMs)) { logout() return } // 初始化活跃时间,启用监控 updateLastActivityTime() isEnabled.value = true } return { start, stop, enable } }

4.2 全局初始化配置(App.vue)

在根组件中完成监控能力初始化、全局方法挂载、未登录状态缓存清理,保证全局唯一性。

<script setup> import { onMounted } from 'vue' import { useIdleLogout, STORAGE_KEY } from '@/composables/useIdleLogout' import { getToken } from '@/utils/auth' // 初始化闲置监控方法 const { start, stop, enable } = useIdleLogout() // 挂载至全局,供页面、登录逻辑调用 window.__startIdleCheck = start window.__stopIdleCheck = stop window.__enableIdleCheck = enable onMounted(() => { // 未登录状态清除残留活跃时间戳,避免脏数据干扰 if (!getToken()) { localStorage.removeItem(STORAGE_KEY) } }) </script>

4.3 主布局集成监控逻辑(layout/index.vue)

在系统核心布局中,等待用户信息加载完成后,根据后端配置动态启用/关闭闲置监控,适配登录后场景。

<script setup> import { onMounted, onUnmounted } from 'vue' import useUserStore from '@/store/modules/user' const userStore = useUserStore() // 挂载后根据用户配置初始化监控 onMounted(async () => { // 确保用户信息加载完成 if (!userStore?.user?.id) { try { await userStore.getInfo() } catch (error) { // 获取用户信息失败(token过期等),停止监控 window.__stopIdleCheck?.() return } } // 后端配置超时时间则启用监控,否则关闭 const idleMinutes = userStore?.user?.idleLogoutMinutes || 0 if (idleMinutes > 0) { window.__startIdleCheck?.() window.__enableIdleCheck?.() } else { window.__stopIdleCheck?.() } }) // 组件卸载时清理事件监听,防止内存泄漏 onUnmounted(() => { window.__stopIdleCheck?.() }) </script>

五、整体执行流程图解

───────────────────────────────────────────────────────────── 应用启动 (App.vue) 1. 初始化 useIdleLogout 监控能力 2. 挂载全局监控方法:start/stop/enable 3. 未登录状态 → 清空本地残留活跃时间戳 ───────────────────────────────────────────────────────────── │ ▼ ───────────────────────────────────────────────────────────── 主布局页面挂载初始化 1. 拉取最新用户信息与后端超时配置 2. 配置有效 → 绑定事件、启用超时校验 3. 配置无效/用户信息失败 → 关闭监控 ───────────────────────────────────────────────────────────── │ ▼ ───────────────────────────────────────────────────────────── 用户登录成功场景 调用全局 __enableIdleCheck() 启用监控 ├─ 读取本地最后活跃时间戳 ├─ 已超时 → 自动执行登出、跳转登录页 └─ 未超时 → 更新活跃时间,开启实时监控 ───────────────────────────────────────────────────────────── │ ▼ ───────────────────────────────────────────────────────────── 实时闲置监控流程 用户触发操作事件 → 执行 onUserActivity() 更新状态 ├─ 监控启用 + 已超时 → 执行登出逻辑 └─ 监控启用 + 未超时 → 刷新本地活跃时间戳 ─────────────────────────────────────────────────────────────

六、配置项说明

配置项/常量

数据来源

功能说明

idleLogoutMinutes

后端用户配置

闲置超时分钟数,0/undefined 表示关闭自动登出功能

STORAGE_KEY

前端常量

本地存储用户最后活跃时间戳的 key,可自定义修改

ACTIVITY_EVENTS

前端常量

监听的用户活跃事件,覆盖桌面端、移动端常规操作

七、关键注意事项与问题规避

7.1 防止内存泄漏

所有监听事件必须在页面/组件卸载时统一解绑,通过onUnmounted调用stop()方法,清空事件监听,避免长期运行的后台系统内存累积泄漏。

7.2 避免事件重复绑定

通过hasBoundEvents状态锁标记事件绑定状态,多次调用start()不会重复绑定 DOM 事件,杜绝事件叠加、重复触发问题。

7.3 防止重复执行登出

新增isLoggingOut登出锁,多事件同时触发超时逻辑时,仅执行一次登出流程,避免接口重复请求、页面重复跳转异常。

7.4 严格把控执行时序

遵循「根组件初始化 - 布局页加载用户配置 - 启用监控」的时序。用户信息获取失败、Token 过期等异常场景,主动关闭监控,避免无效校验。

7.5 浏览器兼容性

所使用的keydownclickscrolltouchstart均为原生标准 DOM 事件,兼容所有现代浏览器,无需额外垫片适配。

八、方案总结

本方案基于 Vue3 组合式 API 实现,核心优势集中在解耦、安全、可扩展、高适配

  1. 逻辑解耦复用:将闲置监控逻辑封装为独立 Composable,与页面视图解耦,全局可复用,维护成本低。

  2. 动态配置灵活:超时时间由后端动态下发,无需修改前端代码,可适配不同用户、不同业务场景的安全策略。

  3. 多页面状态同步:基于 localStorage 实现跨标签页、跨窗口状态共享,解决多页面登录状态不一致问题。

  4. 全方位安全兜底:覆盖页面刷新、异常登出、重复操作等边界场景,彻底规避闲置计时重置的安全漏洞。

  5. 性能稳定可靠:通过多重状态锁规避重复绑定、重复登出、内存泄漏等问题,适配系统长期稳定运行。

该方案已完成线上全量落地,覆盖后台管理系统所有登录页面,可直接复用至各类 Vue3 企业级项目。

← 返回列表