[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-frontend-security-is-not-ui-permission":3,"article-neighbors-frontend-security-is-not-ui-permission":17,"article-related-frontend-security-is-not-ui-permission":39},{"id":4,"slug":5,"title":6,"excerpt":7,"category":8,"tags":9,"content":13,"publishedAt":14,"readingMinutes":15,"published":16},30,"frontend-security-is-not-ui-permission","前端权限安全：为什么仅隐藏 DOM 是无效安全","前端隐藏按钮只是体验优化，安全必须全部下沉到服务端，否则就是给攻击者留后门。","工程实践",[10,11,12],"前端安全","权限","接口设计","我见过不少项目做权限的方式是：前端用 `v-if` 把没权限的按钮隐藏掉，路由再拦截一下，就觉得权限体系做完了。这想法很危险——前端隐藏按钮，本质只是 UX 优化，跟安全一点关系都没有。\n\n为什么这么说？因为前端的所有代码都跑在用户自己的浏览器里，用户想看什么都能看到。你藏起来的按钮，他打开开发者工具改一行 DOM 就出来了；你挡掉的路由，他直接抓包调接口照样访问。最极端的例子，你只在前端隐藏了删除按钮，那攻击者一条 `curl -X DELETE \u002Fapi\u002Fadmin\u002Farticles\u002F1` 就完事了——服务端不校验，这条请求就能成功。\n\n### 安全的基础模型\n\n在讲具体做法之前，得先建立两个安全的基本认知。\n\n第一个是\"客户端不可信\"。凡是跑在用户设备上的代码，都默认可以被篡改。前端的一切——DOM、路由、按钮、localStorage——都只是展示层的把戏，真正的信任边界在服务端。这个原则是 Web 安全的基石，理解了它，你就不会再犯\"前端隐藏=安全\"的错误。\n\n第二个是权限的本质。权限 = 身份验证 + 授权判断。身份验证回答\"你是谁\"，授权判断回答\"你能干什么\"。后者又分两层：功能性权限（能不能访问这个接口）和数据性权限（能不能访问这条数据）。很多人只做了前者，漏了后者——结果就是接口能进去，但可以越权访问别人的数据。这也是 OWASP 十大 Web 安全风险里\"失效的访问控制\"的核心场景。\n\n### 正确的双鉴权模型\n\n正确的做法是前后端双鉴权，各管各的。前端负责\"体验层\"：动态菜单、按钮显隐、路由拦截、无权限提示、提交前二次确认——这些让没有权限的人看不到入口、不白点，但绝不承担安全责任。真正的安全在服务端：全局拦截器里做身份校验、角色匹配、资源归属校验，所有敏感接口必须走完整的权限链路，一个白名单裸奔接口都不能有。\n\n服务端的校验顺序我是这么定的：读 token → 解析用户 → 检查角色 → 检查资源归属 → 检查资源状态 → 执行操作 → 写审计。每一步都不能省。\n\n这里的权限模型，业内标准是 RBAC（基于角色的访问控制）：用户绑定角色，角色绑定权限。更精细的业务还可以升级到 ABAC（基于属性的访问控制），按用户、资源、环境属性动态判断。中小系统用 RBAC 就够，别一上来就上重模型。RBAC 的核心价值是\"解耦\"——用户和权限不直接挂钩，中间隔着角色这层，改权限只要改角色，不用逐个用户调。\n\n### 几个特别容易漏的洞\n\n这里有几个特别容易漏的坑，我重点说：\n\n第一个是越权漏洞（IDOR）。这类漏洞的典型场景是：接口只校验了\"你有没有权限访问这个模块\"，但没校验\"这条数据是不是你的\"。比如 `GET \u002Fapi\u002Fuser\u002Forders\u002F{orderId}`，改一下 orderId 就能看到别人的订单。所有涉及资源的接口，都必须做\"资源归属校验\"——不仅看角色，还要看这条数据跟当前用户的关系。\n\n第二个是批量接口。比如批量删除 `DELETE \u002Fapi\u002Fadmin\u002Farticles`，参数是一串 ID。很多人只校验了第一个 ID 的权限，剩下的直接放行——这是典型越权漏洞。必须逐条校验，循环里每个 ID 都检查一遍，有一个没权限就拒绝。\n\n第三个是 CSRF 和 XSS 的配合。如果权限用 Cookie 携带，要防跨站请求伪造，配合 `SameSite` 属性和 CSRF Token；如果存 localStorage，则要防 XSS 偷取令牌，所有输出做转义，配 CSP 安全头。安全是系统性的，单点防护没用。还有几个基础安全头值得配：CSP（内容安全策略）限制脚本来源、X-Frame-Options 防点击劫持、X-Content-Type-Options 防 MIME 嗅探。\n\n### 最后一步：从攻击者角度测\n\n做完前端权限之后，一定要再用\"直接调接口\"的方式测一遍——绕过所有页面，直接发请求，看服务端到底拦不拦得住。常见的测试清单：越权访问他人资源、批量操作超范围数据、未登录访问受保护接口、越权调用管理接口、修改请求参数绕过校验。拦得住才是真的安全。\n\n一句话总结：前端权限是体验，后端鉴权是基石，二者各司其职。凡是把安全希望寄托在前端隐藏按钮上的项目，都是在给攻击者留后门。安全不是\"看起来做了\"，而是\"从攻击者的角度验证过了\"。\n","2025-10-15T02:00:00Z",6,true,{"prev":18,"next":29},{"id":19,"slug":20,"title":21,"excerpt":22,"category":8,"tags":23,"publishedAt":28,"readingMinutes":15},25,"spring-session-auth-design","SpringBoot 前后端分离 JWT 认证完整落地与坑点","JWT 无状态鉴权落地不难，难的是把密钥、过期时间、敏感数据和全局拦截这些坑都填平。",[24,25,26,27],"Spring Boot","安全","认证","PostgreSQL","2025-08-20T02:00:00Z",{"id":30,"slug":31,"title":32,"excerpt":33,"category":8,"tags":34,"publishedAt":38,"readingMinutes":15},31,"virtual-list-is-not-enough","前端性能优化：虚拟列表真实适用场景与完整优化链路","列表卡顿别急着上虚拟列表，先排查网络、数据、渲染整条链路，最后才是它。",[35,36,37],"Vue","虚拟列表","性能","2025-12-10T02:00:00Z",[40,49,59],{"id":41,"slug":42,"title":43,"excerpt":44,"category":8,"tags":45,"publishedAt":48,"readingMinutes":15},1,"build-a-reliable-personal-site","个人网站工程架构与长期可维护设计","这个站从零搭起来，分享我用 Nuxt 加 Spring Boot 把个人项目做成敢一直改下去的经过。",[46,24,47],"Nuxt","部署","2026-07-20T02:00:00Z",{"id":50,"slug":51,"title":52,"excerpt":53,"category":8,"tags":54,"publishedAt":58,"readingMinutes":15},32,"personal-site-feature-bloat","个人项目如何规避「功能废墟」工程问题","功能越加越多，项目却越来越难改，最后重构比重写还贵——这是我踩过的\"功能废墟\"坑。",[55,56,57],"个人网站","工程管理","维护","2026-05-15T02:00:00Z",{"id":60,"slug":61,"title":62,"excerpt":63,"category":8,"tags":64,"publishedAt":67,"readingMinutes":15},24,"vue-request-cache-implementation","Vue 全局请求缓存工程级实现、过期策略与脏数据规避","给 Vue 项目加全局缓存时踩过的坑：过期策略、脏数据、并发去重，一篇讲清楚。",[35,65,66,37],"TypeScript","缓存","2026-01-25T02:00:00Z"]