架构与实现
卓安Claw 用户身份与权限上下文 v1.0
日期:2026-11-13
问题:用户登录后,Agent 怎么知道“我是谁、在哪个省/哪家律所、什么角色、什么数据范围、什么权限”?
结论:身份不靠用户告诉 Agent,也不靠前端传参;服务端每次请求从签名会话解析用户,再把身份/权限块注入 Agent 系统提示,工具再做服务端校验。
0. 一句话
Agent 不是“一个公共聊天机器人”,而是当前登录用户的办案副驾:它知道服务对象的姓名、角色、律所、省份、数据范围与权限,并且只在这个范围内回答和调用工具。
1. 登录链路
企业微信 OIDC / 本地账号登录
→ 服务端校验
→ 签发 zhuoan_session(签名 token,含 uid)
→ 浏览器持有 HttpOnly Cookie
→ 每次 /message 请求
→ get_session_user() 从 token 解析 uid,查 zhuoan_user
→ get_zhuoan_role() 得到角色
→ 组装身份块 + 案件块
→ Agent 系统提示
→ Agent 工具按同一身份做权限校验
关键点:
- 不以“前端传 role/user”为准。前端只传
case_id、消息文本;身份由服务端从 Cookie 解析。 - 不是登录时把身份写死在 Agent 里。每次请求重新解析,角色/分所/权限改了立即生效。
- 演示模式:没有真实会话时保留
zhuoan_rolecookie 演示角色,但身份块会明确标注“演示角色,非真实登录”。
2. 身份数据模型
zhuoan_user 现有字段(本次新增 province):
| 字段 | 用途 |
|---|---|
id |
用户 ID |
username |
登录名 |
display_name |
姓名/称呼 |
wecom_userid |
企业微信 userid |
role |
客服/客户经理/谈案律师/承办律师/管理者 |
branch |
所属律所/分所(成都总所、重庆分所…) |
province |
省份/区域(可显式填;不填按分所映射) |
department |
部门 |
data_scope |
本人/部门/分所/全所/案件团队 |
is_admin |
是否管理员 |
status |
active/disabled |
省份映射(未显式填 province 时):
| 分所 | 省份 |
|---|---|
| 成都总所 | 四川 |
| 深圳分所 | 广东 |
| 昆明分所 | 云南 |
| 重庆分所 | 重庆 |
| 北京分所 | 北京 |
| 西安分所 | 陕西 |
| 西宁分所 | 青海 |
3. 注入给 Agent 的身份块
服务端生成类似下面的系统提示片段(不是用户消息,前端不可伪造):
## 当前用户身份与权限(服务端注入,前端不可伪造)
- 姓名/称呼:李然
- 账号:liran(企业微信:liran)
- 角色:承办律师
- 所属律所:成都总所
- 省份/区域:四川
- 部门:刑事部
- 数据范围:分所(本分所案件)
- 权限:查看案件、查看案卷材料、编辑清单/记录、推进节点/结案归档、使用 Agent、客户与线索、知识库
- 身份规则:
- 你就是卓安Claw,服务对象是当前用户;不要冒充用户本人、律师或审批人。
- 只能基于当前用户的数据范围检索、引用和回答;不得跨省/跨所读取未授权案件。
- 用户问其他省份/分所的数据时,先说明当前范围;需要跨所时提示走平台/管理员。
- 写操作只能生成“待律师确认”动作,不能直接改案卷、清单、节点或财务。
- C 类案卷只留在本地/私有云;不得把全文发送外部模型,引用要带文书名+页码。
- 不确定权限或数据是否存在时,先调用工具校验,不要猜。
案件会话再追加“当前案件上下文(编号/名称/节点/清单/最近记录)”,两个块用空行拼接。
4. 权限模型
角色权限在 channel/web/api/zhuoan.py 的 ROLE_PERMISSIONS 中定义,身份块只展示当前角色为 True 的权限:
| 权限 | 含义 |
|---|---|
case:view |
查看案件 |
case:file |
查看案卷材料 |
case:edit |
编辑清单/记录 |
case:advance |
推进节点/结案归档 |
agent:run |
使用 Agent |
customer:view |
客户与线索 |
knowledge:view |
知识库 |
cockpit:view |
全所驾驶舱 |
ops:view |
经营分析 |
数据范围独立于角色:
- 本人:仅本人负责/参与的案件
- 部门:本部门案件
- 分所:本分所案件
- 全所:所有分所案件
- 案件团队:仅案件团队成员可见的案件
Agent 工具(zhuoan_case、search_docs、search_knowledge)继续用同一套 can_access_case / _can 服务端校验;身份块只负责“让模型知道边界”,不代替工具校验。
5. 代码落点
| 文件 | 作用 |
|---|---|
channel/web/api/zhuoan_auth.py |
会话、登录、用户表;新增 province 列与 OIDC 字段 |
channel/web/api/zhuoan_identity.py |
身份 payload、省份映射、权限标签、身份块生成 |
channel/web/core/channel.py |
每次 /message 解析会话,注入 zhuoan_identity_context |
bridge/agent_bridge.py |
把身份块 + 案件块拼成 extra_system_suffix |
agent/tools/zhuoan_case/zhuoan_case.py |
新增 whoami,返回当前身份/省份/权限;写操作仍只提议 |
channel/web/api/zhuoan.py |
/api/zhuoan/me 增加 identity/permissions |
6. 登录后 Agent 的实际表现
用户问:
我是谁?我在哪个省?我能看哪些案件?
Agent 应能回答:
- 姓名/账号/角色/所属律所/省份/部门;
- 数据范围(如“仅成都分所案件”);
- 可用权限(如“可查看案卷材料,但不能看经营分析”);
- 不知道时调用
zhuoan_case(method=whoami)校验,而不是猜。
用户问其他省份/分所的案件:
帮我看看重庆分所那个职务侵占案的卷宗。
Agent 应回答:当前账号数据范围是成都分所,无法读取重庆分所案卷;如需跨所,走平台/管理员授权,而不是假装能看到。
7. 安全边界
- 身份块由服务端生成,前端不能传“我是管理者”。
- 会话 token 签名、HttpOnly;角色/分所改动下一次请求生效。
- Agent 工具按同一用户/角色校验;模型说“我是管理员”不改变权限。
- C 类案卷不出本地/私有云;外部模型只拿必要片段。
- 写操作只生成待确认动作,律师确认后才落库并写审计。
- 演示模式明确标注“非真实登录”,避免把演示角色当真实授权。
8. 后续
- 企业微信 OIDC 真实凭证联调,把
wecom_userid→ 账号映射跑通。 - 管理后台增加“省份”字段与数据范围可视化;分所管理员只能管本所账号。
- Agent 回答里显示“当前身份”小卡(姓名/角色/分所/省份),让用户可核对。
- 平台层身份:平台管理员/平台运营/连锁总部与律所角色分开,平台不继承律所案卷权限。
- 增加“身份切换审计”:谁在什么时候以什么身份看了什么。