From 3cfa8d53e3c5c382dca483968172af83c9c70679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=8E=E4=BD=97?= Date: Sat, 13 Jun 2026 01:53:34 +0800 Subject: [PATCH] =?UTF-8?q?docs(bug):=20=E8=AF=B8=E8=91=9B=E4=BA=AE?= =?UTF-8?q?=E5=88=86=E6=9E=90=E6=8A=A5=E5=91=8A=20Bug=20#720?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MD/bugs/BUG_720_ANALYSIS.md | 228 ++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 MD/bugs/BUG_720_ANALYSIS.md diff --git a/MD/bugs/BUG_720_ANALYSIS.md b/MD/bugs/BUG_720_ANALYSIS.md new file mode 100644 index 000000000..9a0602a5b --- /dev/null +++ b/MD/bugs/BUG_720_ANALYSIS.md @@ -0,0 +1,228 @@ +# Bug #720 诸葛亮分析报告 + +> **文档类型**: Bug分析 +> **分析时间**: 2026-06-13 01:53:34 +> **分析模型**: mimo-v2.5 (LLM深度分析) + +--- + +## 基本信息 +- **Bug #**: 720 +- **标题**: 【住院医生工作站】只要打开了一个模块所有的的权限的都可以打开存在安全隐患 +- **模块**: 住院医生工作站 +- **提出人**: 王栩坤 + +--- + +I now have a complete understanding of the bug. Here is my analysis: + +--- + +## 一、Bug 理解 + +**禅道 Bug #720 标题**:【住院医生工作站】只要打开了一个模块所有的权限的都可以打开存在安全隐患 + +**重现步骤**:随便登录一个账号(例:wx,密码:123456),随便打开一个模块,随便切换一个账号(例:doctor1,密码:123456),可以打开 wx 账号的卡片模块。 + +**期望结果**:什么权限下的模块就在什么权限下出现,不应该在别的权限下打开。 + +**附图分析**: +- 截图1(用户"韦雪"):在「医保管理」→「电子处方管理」中查看处方列表 +- 截图2(用户"内科医生1"):登录后左侧导航中「门诊医生工作站」展开但**无「电子处方管理」子菜单**,然而主界面却**依然停留在「电子处方管理」页面**,且数据完全一致 +- 两张截图中,不同角色看到了相同的业务数据和操作按钮,且无任何"无权限"提示 + +**综合总结**:用户 A(护士/药师角色)登录后打开某模块,退出后切换为用户 B(医生角色),用户 B 能直接访问 A 的页面。根因是切换账号后旧路由未被清除、新守卫校验不充分,导致路由级权限绕过。 + +--- + +## 二、根因分析 + +### 核心问题:Vue Router `addRoute()` 是永久性的,切换账号后旧路由从未被移除 + +**完整触发链路**: + +``` +用户A登录 → getInfo()获取A的权限 → generateRoutes() → router.addRoute(A的路由) [永久注册] + ↓ +用户A退出 → logOut() → 清除token/roles/permissions/标签页 → 跳转/login + [⚠️ 未清除: permission store状态、router中已注册的路由] + ↓ +用户B登录 → setToken(B的token) → 跳转/ + ↓ +router.beforeEach → roles.length===0 → getInfo()获取B的权限 → generateRoutes() + → router.addRoute(B的路由) [B的路由被追加,A的路由依然存在] + ↓ +用户B访问/ePrescribing → router.resolve(to).matched.length > 0 ✅(A的路由还在) + → 守卫放行 → 越权访问成功 ❌ +``` + +**三处代码缺陷**: + +| 缺陷 | 文件 | 问题 | +|------|------|------| +| **缺陷1** | `store/modules/permission.js` `generateRoutes()` | 只追加新路由,从不移除旧路由。没有记录已添加的动态路由名 | +| **缺陷2** | `store/modules/user.js` `logOut()` | 只清除 token/roles/permissions/tagsView,**未重置 permission store**(routes/sidebarRouters等)| +| **缺陷3** | `permission.js` 路由守卫 | 最终校验只检查 `router.resolve(to).matched.length === 0`(路由是否注册),**不检查当前用户是否有权访问该路由** | + +**注意**:路由守卫中已有注释"铁律: 路由权限校验 — 防止切换账户后通过旧标签或直接输入URL访问无权限页面",说明开发者**意识到了这个问题但修复不彻底**——因为旧路由从未被清除,所以 `matched.length` 永远 > 0,校验形同虚设。 + +--- + +## 三、修复方案 + +### 方案:三层防御(清除旧路由 + 重置状态 + 守卫增强) + +#### 修改1:`healthlink-his-ui/src/store/modules/permission.js` — 记录并清除旧路由 + +```javascript +// 新增 state +state: () => ({ + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [], + // 新增:记录所有动态添加的路由名,用于清理 + addedRouteNames: [] +}), + +// 新增 action:清除所有动态路由 +actions: { + removeAddedRoutes() { + this.addedRouteNames.forEach(name => { + try { router.removeRoute(name) } catch(e) {} + }) + this.addedRouteNames = [] + }, + + generateRoutes(roles) { + return new Promise(resolve => { + // 【修复】生成新路由前,先清除所有旧的动态路由 + this.removeAddedRoutes() + + getRouters().then(res => { + const sdata = JSON.parse(JSON.stringify(res.data)) + const rdata = JSON.parse(JSON.stringify(res.data)) + const defaultData = JSON.parse(JSON.stringify(res.data)) + const sidebarRoutes = filterAsyncRouter(sdata) + const rewriteRoutes = filterAsyncRouter(rdata, false, true) + const defaultRoutes = filterAsyncRouter(defaultData) + const asyncRoutes = filterDynamicRoutes(dynamicRoutes) + + // 记录并添加路由 + const addedNames = [] + asyncRoutes.forEach(route => { + router.addRoute(route) + if (route.name) addedNames.push(route.name) + }) + addNotFoundRoute() + + // 记录后端动态路由名 + this.trackAddedRoutes(rewriteRoutes, addedNames) + + this.setRoutes(rewriteRoutes) + this.setSidebarRouters(constantRoutes.concat(sidebarRoutes)) + this.setDefaultRoutes(sidebarRoutes) + this.setTopbarRoutes(defaultRoutes) + resolve(rewriteRoutes) + }).catch(err => { + console.error('获取路由失败:', err) + addNotFoundRoute() + this.setRoutes([]) + resolve([]) + }) + }) + }, + + // 新增:递归追踪所有动态添加的路由名 + trackAddedRoutes(routes, names) { + routes.forEach(route => { + if (route.name) names.push(route.name) + if (route.children) this.trackAddedRoutes(route.children, names) + }) + this.addedRouteNames = [...new Set(names)] + } +} +``` + +#### 修改2:`healthlink-his-ui/src/store/modules/user.js` — logOut 时重置权限状态 + +```javascript +import usePermissionStore from '@/store/modules/permission' + +// 在 logOut action 中增加: +logOut() { + return new Promise((resolve, reject) => { + logout(this.token).then(() => { + this.token = '' + this.roles = [] + this.permissions = [] + this.tenantId = '' + removeToken() + try { useTagsViewStore().delAllViews() } catch(e) {} + // 【修复】清除所有动态路由,防止旧用户路由残留 + try { usePermissionStore().removeAddedRoutes() } catch(e) {} + try { usePermissionStore().$reset() } catch(e) {} + resolve() + }).catch(error => { + reject(error) + }) + }) +} +``` + +#### 修改3:`healthlink-his-ui/src/permission.js` — 增强路由守卫权限校验 + +在现有的 `resolved.matched.length` 检查之后,增加基于用户权限的二次校验: + +```javascript +// 在 "return true" 之前,增加权限校验 +// 获取目标路由对应的菜单路径 +const targetPath = to.path +const sidebarRoutes = usePermissionStore().sidebarRouters +const allPaths = collectAllPaths(sidebarRoutes) // 递归收集所有已授权路径 +if (allPaths.size > 0 && !allPaths.has(targetPath) && !isConstantPath(targetPath)) { + ElMessage.warning('无权访问该页面') + return { path: '/' } +} +return true +``` + +其中辅助函数: +```javascript +// 常量路由(始终允许访问)不需要权限校验 +function isConstantPath(path) { + const constantPaths = ['/', '/index', '/login', '/register', '/401', '/lock', '/user/profile', '/redirect'] + return constantPaths.some(p => path === p || path.startsWith(p + '/')) +} + +// 递归收集 sidebarRouters 中所有路由路径 +function collectAllPaths(routes) { + const paths = new Set() + function walk(items) { + items.forEach(r => { + if (r.path) paths.add(r.path.startsWith('/') ? r.path : '/' + r.path) + if (r.children) walk(r.children) + }) + } + walk(routes) + return paths +} +``` + +--- + +## 四、路由决策 + +**FIXER**: guanyu(后端开发 + 通用修复) + +**REASON**: 此 Bug 的修复**全部在前端**(`permission.js` 路由守卫、`permission.js` store、`user.js` store),涉及 Vue Router 路由生命周期管理和 Pinia store 状态管理,属于前端核心逻辑修改。虽然分类为"后端开发"的关羽,但此任务本质是前端路由/权限架构修复,**更应交给 zhaoyun(前端开发)**。因为需要修改 3 个前端核心文件(`permission.js` store、`user.js` store、路由守卫 `permission.js`),涉及 Vue Router 4 的 `addRoute/removeRoute` 生命周期、Pinia store 重置、路由守卫权限校验等前端专属知识。 + +--- + +## 路由决策 +- **FIXER_ID**: guanyu +- **修复 Agent**: guanyu(后端) +- **原因**: LLM 分析决策 + +> ⚠️ 修复人员请先验证以上分析是否正确,再执行修复。