返回技术文章

工程实践

前端权限安全:为什么仅隐藏 DOM 是无效安全

前端隐藏按钮只是体验优化,安全必须全部下沉到服务端,否则就是给攻击者留后门。

我见过不少项目做权限的方式是:前端用 v-if 把没权限的按钮隐藏掉,路由再拦截一下,就觉得权限体系做完了。这想法很危险——前端隐藏按钮,本质只是 UX 优化,跟安全一点关系都没有。

为什么这么说?因为前端的所有代码都跑在用户自己的浏览器里,用户想看什么都能看到。你藏起来的按钮,他打开开发者工具改一行 DOM 就出来了;你挡掉的路由,他直接抓包调接口照样访问。最极端的例子,你只在前端隐藏了删除按钮,那攻击者一条 curl -X DELETE /api/admin/articles/1 就完事了——服务端不校验,这条请求就能成功。

安全的基础模型

在讲具体做法之前,得先建立两个安全的基本认知。

第一个是"客户端不可信"。凡是跑在用户设备上的代码,都默认可以被篡改。前端的一切——DOM、路由、按钮、localStorage——都只是展示层的把戏,真正的信任边界在服务端。这个原则是 Web 安全的基石,理解了它,你就不会再犯"前端隐藏=安全"的错误。

第二个是权限的本质。权限 = 身份验证 + 授权判断。身份验证回答"你是谁",授权判断回答"你能干什么"。后者又分两层:功能性权限(能不能访问这个接口)和数据性权限(能不能访问这条数据)。很多人只做了前者,漏了后者——结果就是接口能进去,但可以越权访问别人的数据。这也是 OWASP 十大 Web 安全风险里"失效的访问控制"的核心场景。

正确的双鉴权模型

正确的做法是前后端双鉴权,各管各的。前端负责"体验层":动态菜单、按钮显隐、路由拦截、无权限提示、提交前二次确认——这些让没有权限的人看不到入口、不白点,但绝不承担安全责任。真正的安全在服务端:全局拦截器里做身份校验、角色匹配、资源归属校验,所有敏感接口必须走完整的权限链路,一个白名单裸奔接口都不能有。

服务端的校验顺序我是这么定的:读 token → 解析用户 → 检查角色 → 检查资源归属 → 检查资源状态 → 执行操作 → 写审计。每一步都不能省。

这里的权限模型,业内标准是 RBAC(基于角色的访问控制):用户绑定角色,角色绑定权限。更精细的业务还可以升级到 ABAC(基于属性的访问控制),按用户、资源、环境属性动态判断。中小系统用 RBAC 就够,别一上来就上重模型。RBAC 的核心价值是"解耦"——用户和权限不直接挂钩,中间隔着角色这层,改权限只要改角色,不用逐个用户调。

几个特别容易漏的洞

这里有几个特别容易漏的坑,我重点说:

第一个是越权漏洞(IDOR)。这类漏洞的典型场景是:接口只校验了"你有没有权限访问这个模块",但没校验"这条数据是不是你的"。比如 GET /api/user/orders/{orderId},改一下 orderId 就能看到别人的订单。所有涉及资源的接口,都必须做"资源归属校验"——不仅看角色,还要看这条数据跟当前用户的关系。

第二个是批量接口。比如批量删除 DELETE /api/admin/articles,参数是一串 ID。很多人只校验了第一个 ID 的权限,剩下的直接放行——这是典型越权漏洞。必须逐条校验,循环里每个 ID 都检查一遍,有一个没权限就拒绝。

第三个是 CSRF 和 XSS 的配合。如果权限用 Cookie 携带,要防跨站请求伪造,配合 SameSite 属性和 CSRF Token;如果存 localStorage,则要防 XSS 偷取令牌,所有输出做转义,配 CSP 安全头。安全是系统性的,单点防护没用。还有几个基础安全头值得配:CSP(内容安全策略)限制脚本来源、X-Frame-Options 防点击劫持、X-Content-Type-Options 防 MIME 嗅探。

最后一步:从攻击者角度测

做完前端权限之后,一定要再用"直接调接口"的方式测一遍——绕过所有页面,直接发请求,看服务端到底拦不拦得住。常见的测试清单:越权访问他人资源、批量操作超范围数据、未登录访问受保护接口、越权调用管理接口、修改请求参数绕过校验。拦得住才是真的安全。

一句话总结:前端权限是体验,后端鉴权是基石,二者各司其职。凡是把安全希望寄托在前端隐藏按钮上的项目,都是在给攻击者留后门。安全不是"看起来做了",而是"从攻击者的角度验证过了"。

分享这篇文章

如果你在这里有了 10 分钟的顿悟,那它折叠得刚刚好。