场景很常见:老板说"给我开个账号,我想时不时看看告警"。
你的手指悬在键盘上——开,还是不开?
- 开 admin?他手一滑能删资产、改配置
- 不开?每周期截图发他,你成了人肉报表机
答案应该是:开一个 viewer 账号,三秒钟,然后忘掉这件事。
这一篇讲 AI-miniSOC 的权限体系,按你最关心的三件事组织:权限有什么用(四个角色能干什么)、怎么实现的(凭什么敢放心)、效果如何(实测数据说话)。
一、权限有什么用:四个角色,开箱即用
角色全景
| 角色 | 定位 | 能看 | 能动 | 典型用户 |
|---|---|---|---|---|
| admin 👑 | 全权管理员 | 全部 41 个菜单 | 一切(含用户/角色/系统配置) | 你自己 |
| operator 🔧 | 运维干活的 | 18 个业务菜单 | 资产编辑、稽核裁决、生成报告、跑扫描 | 运维同事 / 外包驻场 |
| viewer 👁️ | 纯只读 | 16 个菜单(资产/告警/报告/扫描) | 零——一个写按钮都没有 | 老板 / 甲方 / 想看的人 |
| auditor 🧾 | 审计查账 | viewer 全集 + 行为画像 + 审计日志 | 零(审计日志可看) | 安全审计 / 合规检查 |
权限精确到按钮级。同一个"资产稽核"页面:operator 看到 查看 / 对账 / 裁决 / 出报告 四个按钮,viewer 只看到只读视图,auditor 能看能查但不能改。不是前端藏按钮的把戏——每个按钮背后都是独立的权限码,API 层逐个校验。

三个高频场景
场景一:老板要看。 开 viewer。他登进去看到仪表板、资产、告警、报告——全是真实数据,但"生成报告"按钮是灰的;他手痒直接调 API?403,审计日志记一笔。你不用给他做任何解释,系统替你说"不"。
场景二:审计要查。 开 auditor。比 viewer 多两样东西:行为画像(谁在什么时间访问了什么)和审计日志(所有写操作谁做的、什么时候、改了什么)——审计员自己能取证,不用你导出日志。
场景三:给 AI Agent 发权限。 13 篇讲过 MCP Key——Agent 拿 Key 以绑定用户身份行事。权限矩阵就是 Agent 的活动边界:给 workbuddy 绑 operator,它能帮你建工单;绑 viewer,它只能查不能动。人用人和 Agent 用,同一套规则,不用维护两套。
二、怎么实现的:双层控制 + 三道拦截
2.1 双层控制:界面管体验,API 管安全
权限分两层,各管各的:
- 菜单/按钮层:控制界面呈现——viewer 登录后左侧菜单只有 16 项,写按钮不渲染。这层管的是体验:让用户看不到用不着的东西。
- API 端点层:控制真正的访问——就算 viewer 绕过界面直接 curl 调写接口,服务端照样 403。这层管的是安全。
永远以 API 层为准。 前端隐藏只是体验优化——真正的边界在服务端。这是整套体系的第一原则:界面可以骗人,API 不会。
2.2 三道拦截:一个请求要过三道关
任何一个写请求(POST/PUT/PATCH/DELETE)从到达服务到落库,要依次过三道关卡:

第一道:登录校验。 所有业务路由统一挂在集中注册点(include_human_router),登录态校验全覆盖。哪些接口可以匿名访问?只有白名单里的那几个(登录、验证码、健康检查、API 文档)——精确到 method + path,改白名单必须过安全评审。公开是显式决策,不是遗漏。
第二道:显式授权。 每个写路由必须声明"谁能调"——挂在 require_admin / require_role / require_button_permission 上,并且带机器可读的 AuthSpec 标记。这意味着"这个端点有没有授权声明"是 CI 能自动验证的,不靠人眼 review。
第三道:集中兜底——写侧默认拒绝。 这是整套设计的心脏。没挂授权声明的写请求,在第三道被无条件拦截:
写请求 → 验登录 → 验显式授权 → 都过了才触达业务
↓ 没声明授权
默认 403 + 落审计
为什么要这道兜底?因为人会忘。新加一个接口忘了写鉴权——在"默认放行"的世界里,这是一个静默的漏洞躺着等人踩;在"默认拒绝"的世界里,这只是一个功能不可用的小 bug,测试立刻发现,补一行声明就好。
拦错是显性的,用起来就发现;放行错是隐性的,发现时已经晚了。
唯一的例外是登出(/auth/logout 也是写操作,但谁都得能登出)——例外同样显式登记在案。
2.3 权限矩阵怎么管:像代码一样版本化
“谁能看什么"这份配置本身也会腐化——手工在界面上点来点去,三个月后没人说得清当前矩阵长什么样、是不是还是设计的样子。
AI-miniSOC 的做法是把矩阵变成版本化资产:一份基线文件(JSON)定义每个角色的菜单+按钮全景,幂等脚本负责把基线对齐到数据库,跑完输出漂移报告。改权限 = 改基线文件 + 评审 + 跑脚本,和改代码一样走 git——每一次权限变更都有 commit 可查、可回滚、可审计。
2.4 两个值得知道的细节
角色变更即时生效。 改了某用户的角色,他手里的旧 token 立刻按新权限执行——不用等他退出重登。原理是每次请求都回查数据库取最新角色,不信任 token 里的旧快照。
被拒的请求也落审计。 越权尝试写入审计日志——谁、什么时候、试图调什么接口。这不只是追责,拒绝本身是情报:一个 viewer 账号反复尝试写操作,这个信号值得你主动看一眼。
三、效果如何:实测说话
3.1 角色行为实测矩阵
拿真实账号对着运行实例逐项测(合法载荷,让请求真正走到权限层):
| 操作 | 匿名 | viewer | operator | admin |
|---|---|---|---|---|
| GET 资产 / 告警 / 报告 | 401 拒绝 | 200 ✅ | 200 ✅ | 200 ✅ |
| POST 创建资产 / 建事件 | 401 拒绝 | 403 + 落审计 | 200 ✅ | 200 ✅ |
| DELETE 删除资产 | 401 拒绝 | 403 + 落审计 | 403(需授权) | 200 ✅ |
| 用户/角色/系统配置管理 | 401 拒绝 | 403 | 403 | 200 ✅ |
| 审计日志 | 401 拒绝 | 403 | 403 | 200(auditor 也可) |
五个维度全部符合预期:匿名进不来、viewer 看得了动不了、operator 干业务碰不了系统、admin 全能、auditor 专享审计。
落到界面上。系统管理里的角色列表——每个角色的定位一句话说清(“运维:资产读写 + 对账/报告”,“观察者:仅看资产/告警/报告”),开账号时照着描述选就行:

点开"观察者"角色的权限配置,能看到这套粒度长什么样——菜单树逐项勾选,叶子节点上挂按钮级权限。注意细节:查看 勾了、AI 解读 勾了(只读性质的分析,对观察者开放),但 执行巡检、重建边、导出 全是空的——同一个页面里,看和动是两排开关:

这就是"按钮级权限"的直观形态:不是把整个页面锁起来,而是页面里的每个动作独立开关。观察者能进资产图谱页看拓扑,但"重建边"这种会动数据的操作,界面上就不亮。
3.2 整改前后的对照
这套体系上线前后的差异,一组 curl 就能量化(整改前匿名 GET 全量资产/告警返回 200,viewer 能真实建事件):
| 指标 | 整改前 | 整改后 |
|---|---|---|
| 匿名可读的敏感接口 | 10 个模块(资产/告警/事件/仪表板…) | 0(白名单外全部 401) |
| viewer 调写接口 | 200 真实落库 | 403 + 落审计 |
| 忘写鉴权的新端点 | 静默放行(漏洞) | 默认拒绝(可用性小 bug) |
| 权限矩阵状态 | 手工配置、漂移不可知 | 版本化基线 + drift 报告 |
| 防复发 | 无(CI 测试 continue-on-error) | 38 个安全测试阻断式门禁 |
3.3 防烂回去:CI 门禁
机制建好后的最后一问:三个月后新同事加了个模块忘了挂鉴权,谁拦?
CI 里跑着一套阻断式安全门禁(红了合不进去):每条路由必须三分类(公开/机器/人类)、每个写端点必须有 AuthSpec、viewer 写必须 403 且落审计、基线导出必须与文件零漂移。这四条断言把"权限完整性"从 review 时的自觉变成了合并前的硬门槛。
踩坑记录:三个真实翻车点
坑一:批量重构漏挂载,五个页面集体 404。 路由统一挂载点改造时漏了一个模块(browsing)——只 import 没挂载,“行为分析"下五个子菜单全部 404。教训:集中挂载重构必须配注册完整性测试——现在有专门测试列出每个模块的全部端点断言已注册。批量重构"漏一个"是宿命,回归测试是兜底。
坑二:菜单相对路径导致跨菜单越权。 菜单表里 path 存相对名(list),而资产和报告下都有叫 list 的子菜单——按钮权限按 path 平铺匹配,在一个 list 有权限等于在所有 list 都有。修法:定位符改绝对路径(/assets/list)。教训:权限定位符必须是全局唯一坐标,相对名在同一棵树里就是歧义源。
坑三:用非法载荷测权限,测了个寂寞。 早期测越权用非法 payload,返回 422 就以为"没被拦截”——其实参数校验先于权限执行,422 说明请求根本没走到权限层。修法:用合法载荷测,看它是被 403 拦下还是 200 成功。测权限时,“失败的原因"比"失败本身"重要。
为什么一个人的 SOC 也要认真做权限
“就我一个人用,直接 admin 不就完了?"——三个理由说不:
- 第二个用户来得比你想象中快。老板要看、审计要查、外包要开临时号——等那天来临再补权限,就是在生产环境动大手术。
- 权限是最难还的债。功能缺失可以后加;权限模型后加要动每一层。早一天立规矩,少还十倍债。
- Agent 时代用户更多了。每个 MCP Key 都是一个"用户”——权限矩阵同时是人和 Agent 的活动边界,一套体系管两类身份。
鉴权不该是开发者的自觉动作,该是框架的默认行为。 自觉会遗忘、会疲劳;框架默认不会。
常见问题
Q:为什么是单角色模型,不做多角色复合(一人兼运维+审计)?
A:有意的取舍。角色就四个、用户个位数,复合授权是过度设计。真需要"既运维又审计"时,第一个动的是这个结构约束。不为想象中的需求增加今天的复杂度。
Q:写侧默认拒绝会不会把合法功能也拦了?
A:会拦到,但拦错是显性的——功能测试立刻发现,补一行 AuthSpec 就好;而默认放行拦错是隐性的——漏洞静静躺着等人发现。两害相权取显性。
Q:菜单权限和端点权限两套体系,怎么对齐?
A:两层各管各的(见 2.1),永远以 API 层为准。前端隐藏是体验优化,不是安全边界——真被绕过时,拦住你的是 403,不是看不见的按钮。
Q:数据级权限呢?部门 A 只看自己部门的资产?
A:留了口子(角色上的 data_scope 字段:全部/本部门/仅自己),单部门场景下暂无需求,先不做。权限建设按真实需求排序,不按完备性排序。
Q:怎么给我的系统加这套?
A:四步:① 定角色——从真实用户画像出发(谁看/谁干/谁管/谁查),别超过 5 个;② 端点层先行——写接口逐个挂显式授权;③ 上默认拒绝——让漏配从漏洞变成 bug;④ CI 门禁收尾。顺序不能反:先有显式授权再翻默认值,否则全线瘫痪;先翻默认值再上门禁,否则烂回去只是时间问题。
收尾:15 在系列中的位置
| 系列主线 | 篇目 |
|---|---|
| AI 副手五部曲 | 09-13 |
| 工程化·告警治理 | 14 |
| 工程化·权限矩阵(信任边界) | 15(本篇) |
| 工程化·CI/CD | 16 |
14 篇守住注意力,15 篇守住信任边界——谁进得来、能看什么、能动什么,每个"不"都有架构背书。16 篇讲 CI/CD——这套安全门禁就跑在那条流水线上。
老板上周要的账号,今天开好了。viewer 角色。 他登进去:仪表板、资产、告警、报告——都能看,全是真实数据。 他想点"生成报告”——按钮是灰的。他手痒调了下 API——403,审计日志里多了一条记录。 你没收到任何告警,因为这本就不值得告警——边界之内是服务,边界之外是 403,仅此而已。
权限做好的标志不是拦住了多少越权,是你可以放心把账号交给任何人——包括未来的自己。
【系列导航】《一个人的SOC》总目录(持续更新)
【上一篇】(14) 告警治理——聚合、分级与 13/10/7/4 阈值统一的故事
【系列开篇】(00) 中小企业为什么需要自己的安全运营中心
【项目地址】AI-miniSOC - GitHub · MIT 开源,欢迎 Star ⭐
【Gitee 镜像】 https://gitee.com/xiejava/AI-miniSOC
【交流】 拿 curl 扫一遍你系统的 GET 端点(不带 token),有多少个 200?欢迎来 Issue 对答案——数字可能比你预期的大。
下一篇(预告):(16) CI/CD 全链路——self-hosted runner 与 1.5 分钟自动回滚(连载中,敬请关注)
作者博客:http://xiejava.ishareread.com/

关注:微信公众号,一起学习成长!