不能用 hidden 或 style.display 做权限控制,因它们仅隐藏视觉元素而不阻断实际访问能力;真实权限必须由后端每次请求校验,前端仅依据后端下发的结构化权限数据渲染。

前端用 hidden、style.display 或 class 切换控制按钮/菜单显隐,本身没有问题;但一旦把它当成“权限控制”,系统就已在生产环境裸奔。
为什么不能用 hidden 或 style.display 做权限隔离
它们只是视觉开关,不阻断任何实际能力:
-
hidden属性可被 DevTools 一键删除,元素立刻可见且可点击 -
style.display = "none"同样能被 JS 动态改回"block",甚至直接执行document.getElementById("delete-btn").click() - 用户绕过页面,用
curl -X POST /api/users/123/delete直调接口——只要后端没鉴权,照样删除成功
真实权限必须由后端每次请求时校验,前端只负责“按结果渲染”。不是“隐藏了就安全”,而是“后端说不能,我才不画那个按钮”。
权限数据必须从后端结构化下发,而非本地推导
常见错误是把权限写死在 JS 里,或从 localStorage 读 "role: admin" 字符串——这等于把钥匙挂在门把手上。
正确做法是:页面首次加载时,由后端注入或 API 返回明确的权限集合,例如:
{ "permissions": ["user:read", "user:edit", "order:delete"], "menus": ["dashboard", "users", "orders"] }
之后所有显隐逻辑都基于这个对象判断,比如:
if (perms.permissions.includes("user:delete")) {<br> document.getElementById("delete-btn").classList.remove("hidden");<br>}
注意:这个 perms 对象必须来自可信上下文(如 Thymeleaf 模板变量、GET /api/me 响应),不能由前端拼接、计算或缓存复用。
Shiro + Thymeleaf 场景下,shiro:hasPermission 是服务端渲染,不是前端开关
像 <haspermission name="user:add"><button>新增</button></haspermission> 这类标签,本质是 Thymeleaf 在服务端解析时就决定是否输出该 HTML 片段。浏览器收到的响应里,压根不存在无权限的按钮 DOM。
这意味着:
- 无需前端 JS 控制显隐,也不存在“先渲染再隐藏”的闪烁或可探测痕迹
- 依赖
thymeleaf-extras-shiro扩展包,且必须配置ShiroDialect - 它和前端 JS 的
display完全无关——后者只适合做 UI 状态切换(如折叠面板),不适用于权限
表格列级显隐必须绑定语义化标识,而非索引
后台管理系统中常需按角色隐藏某些敏感列(如“薪资”、“身份证号”)。若用 td:nth-child(5) 这类位置选择器,一旦后端调整字段顺序,前端权限逻辑就集体失效。
可靠做法是给每列打上唯一语义标识:
<th data-permission="salary:read">月薪</th><br><td data-permission="salary:read">15000</td>
然后用权限数据驱动批量操作:
perms.permissions.includes("salary:read")<br> ? document.querySelectorAll("[data-permission='salary:read']").forEach(el => el.classList.remove("hidden"))<br> : document.querySelectorAll("[data-permission='salary:read']").forEach(el => el.classList.add("hidden"));
关键点:data-permission 值必须与后端返回的权限字符串完全一致,且不能靠 JS 拼接生成。
最容易被忽略的环节不是按钮显隐,而是表单提交、AJAX 请求、URL 跳转这些“看不见的通道”——只要后端接口没校验 user.hasPermission("xxx"),前端做得再严密,也只是一层窗户纸。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











