# 四 Agent 统一 JWT + RBAC 鉴权手册 > 需求拆解阶段产物 · 2026-09-05 > 依据:`业务场景优先级清单` F-01、`数据交互矩阵` §8、`Agent风险与合规约束汇总` G-01 > 适用范围:**客户财富 / 代理人助手 / 数据分析 / 风控监测** 四个 Agent 及共用底座 > 目标:**一套身份源、一套 Token 规范、一套 RBAC 模型、一套数据归属校验**,禁止各 Agent 自建鉴权 --- ## 1. 设计原则 | 编号 | 原则 | 说明 | | --- | --- | --- | | P-01 | **统一入口** | 所有 Agent HTTP/WebSocket/API 请求经 **Agent Gateway(或共用 Auth SDK)** 校验,业务服务不自行解析裸 JWT | | P-02 | **身份与权限分离** | JWT 只携带 **身份(who)** 与 **角色(roles)**;**数据归属(which customer)** 在数据层二次校验 | | P-03 | **最小权限** | 默认拒绝;显式授予 `permission` 才允许读/写/审计 | | P-04 | **Agent 边界** | Token 绑定 `agent_type`;客户 Token **不能**调代理人 Agent;内部 Token **不能**冒充客户 | | P-05 | **全量留痕** | 鉴权失败、越权尝试必须写 `audit_log` + `input_guard_log`(`illegal_param` / 自定义 `auth_denied`) | | P-06 | **与 Core 对齐** | 员工角色、客户 ID、代理人-客户归属以 **Core RBAC + `customer_advisor_rel`** 为准,Agent 库不维护第二套账号体系 | --- ## 2. 架构总览 ```text ┌──────────────┐ ┌──────────────┐ ┌─────────────────────────────┐ │ C 端 App │ │ 内部工作台 │ │ 交易前钩子 / 批处理 Job │ │ (客户登录) │ │ (员工 SSO) │ │ (Service Account) │ └──────┬───────┘ └──────┬───────┘ └──────────────┬──────────────┘ │ │ │ └────────────────────┴──────────────────────────────┘ │ ┌───────────▼───────────┐ │ Identity Provider │ │ (Core / 统一 IdP) │ └───────────┬───────────┘ │ 签发 JWT ┌───────────▼───────────┐ │ Agent Gateway │ │ ① 验签 ② 过期 ③ RBAC │ │ ④ 注入 AuthContext │ └───────────┬───────────┘ ┌─────────────────────┼─────────────────────┐ │ │ │ ┌──────▼──────┐ ┌───────▼───────┐ ┌───────▼───────┐ │ 客户财富 │ │ 代理人助手 │ │ 数据分析 │ │ Agent │ │ Agent │ │ Agent │ └──────┬──────┘ └───────┬───────┘ └───────┬───────┘ │ │ │ └─────────────────────┼─────────────────────┘ │ ┌───────────▼───────────┐ │ Data Access Layer │ │ ⑤ 归属校验 │ │ ⑥ 画像 L1/L2/L3 RBAC │ └───────────────────────┘ ``` **两层校验(必须都做)** 1. **网关层 RBAC**:角色是否允许访问该 Agent、该 API、该 Tool。 2. **数据层归属**:操作的目标 `customer_id` / `session_id` 是否属于当前操作者(代理人名下、客户本人等)。 仅做第 1 层而不做第 2 层,视为 **F-01 未实现**。 --- ## 3. 身份来源与 Token 类型 | Token 类型 | 签发方 | 使用者 | `sub` 含义 | 典型 `agent_type` 入口 | | --- | --- | --- | --- | --- | | `customer` | C 端登录 / Core 客户中心 | 客户本人 | `customer_id` | 仅 **客户财富 Agent** | | `staff` | 内部 SSO / Core HR | 员工 | `staff_id` | 代理人 / 分析 / 风控 / 合规 | | `service` | 平台密钥 + mTLS | 系统 Job、交易前钩子 | `service_name` | 风控 R-02 钩子、批处理 | > **禁止**:用 `staff` Token 访问客户 Agent 接口冒充客户;用 `customer` Token 访问内部 Agent。 --- ## 4. JWT 规范(四 Agent 共用) ### 4.1 Header ```json { "alg": "RS256", "typ": "JWT", "kid": "2026-09-key-1" } ``` - 算法:**RS256**(公钥网关校验,私钥仅 IdP 持有)。 - 不支持 `none`、HS256 对称密钥(除非开发环境显式开关)。 ### 4.2 Payload 标准 Claims | Claim | 必填 | 类型 | 说明 | | --- | --- | --- | --- | | `iss` | 是 | string | 签发者,如 `https://idp.jinrong.internal` | | `sub` | 是 | string | 主体 ID:`customer_id` 或 `staff_id` 或 `service_name` | | `aud` | 是 | string[] | 受众,如 `["agent-gateway"]` | | `exp` | 是 | number | 过期时间(Unix 秒) | | `iat` | 是 | number | 签发时间 | | `jti` | 是 | string | Token 唯一 ID,便于吊销与审计 | | `token_type` | 是 | enum | `customer` \| `staff` \| `service` | | `roles` | 是 | string[] | RBAC 角色,见 §5 | | `permissions` | 否 | string[] | 细粒度权限(可选;推荐员工 Token 携带) | | `tenant_id` | 是 | string | 租户/法人主体,多法人场景隔离 | | `customer_id` | 条件 | string | **仅 `token_type=customer`**:必须等于 `sub` | | `advisor_id` | 条件 | string | **代理人角色**:当前员工 ID,等于 `sub` | | `session_hint` | 否 | string | 可选,绑定 C 端设备/session,防 Token 盗用 | ### 4.3 员工 Token 示例 ```json { "iss": "https://idp.jinrong.internal", "sub": "STAFF-10086", "aud": ["agent-gateway"], "exp": 1735689600, "iat": 1735686000, "jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "token_type": "staff", "tenant_id": "TENANT-001", "roles": ["advisor"], "permissions": [ "agent:advisor:chat", "profile:l1:read", "profile:l2:write", "profile:l3:read", "core:holding:read" ], "advisor_id": "STAFF-10086" } ``` ### 4.4 客户 Token 示例 ```json { "iss": "https://idp.jinrong.internal", "sub": "CUST-9527", "aud": ["agent-gateway"], "exp": 1735689600, "iat": 1735686000, "jti": "f0e1d2c3-b4a5-6789-0123-456789abcdef", "token_type": "customer", "tenant_id": "TENANT-001", "roles": ["customer"], "permissions": [ "agent:customer:chat", "profile:l1:read", "profile:l1:write", "core:holding:read:self" ], "customer_id": "CUST-9527" } ``` ### 4.5 服务账号 Token(交易前适当性 R-02) ```json { "token_type": "service", "sub": "svc-trade-suitability", "roles": ["service_risk"], "permissions": [ "agent:risk:suitability_check", "profile:l1:read", "profile:l2:read", "audit:write" ] } ``` - 有效期建议 **≤ 5 分钟**,且绑定调用方 IP / mTLS 证书。 ### 4.6 请求头约定 | Header | 必填 | 说明 | | --- | --- | --- | | `Authorization` | 是 | `Bearer ` | | `X-Trace-Id` | 是 | 全链路 ID;网关可补发 | | `X-Agent-Type` | 是 | `customer` \| `advisor` \| `analyst` \| `risk` | | `X-Request-Id` | 否 | 幂等 / 重试 | 网关校验 **`X-Agent-Type` 与 Token 允许访问的 Agent 列表一致**,不一致直接 403。 --- ## 5. RBAC 角色与权限模型 ### 5.1 角色定义(`roles`) | 角色代码 | 中文名 | 可进入 Agent | 说明 | | --- | --- | --- | --- | | `customer` | 客户本人 | 客户财富 | 仅本人数据 | | `advisor` | 理财代理人 | 代理人助手 | 名下客户 | | `analyst` | 数据分析员 | 数据分析 | 内部只读 + 聚合 | | `risk_officer` | 风控专员 | 风控监测 | 预警处置、L3 写 | | `compliance` | 合规专员 | 代理人(审计台)、共用审计 API | **只读审计**,不看客户业务对话内容除非授权 | | `ops` | 运营 | 代理人(A-08 统计) | 仅脱敏聚合 | | `service_risk` | 风控服务账号 | 风控 API | 交易前钩子,无人机交互 | 一人可多角色,如 `["advisor", "compliance"]`;权限取 **并集**,数据归属仍按 **最窄范围** 校验。 ### 5.2 权限命名规范 ```text {scope}:{resource}:{action}[:{qualifier}] ``` | 前缀 scope | 示例 | 含义 | | --- | --- | --- | | `agent` | `agent:customer:chat` | 访问某 Agent 的对话能力 | | `profile` | `profile:l2:write` | 画像 L1/L2/L3 | | `core` | `core:holding:read` | Core 只读业务数据 | | `risk` | `risk:alert:write` | 预警写入 | | `audit` | `audit:read:all` | 合规审计读 | | `sql` | `sql:execute:readonly` | 分析 Agent 只读 SQL | ### 5.3 角色 → 默认权限包 | 角色 | 默认 permissions(摘要) | | --- | --- | | `customer` | `agent:customer:chat`, `profile:l1:read`, `profile:l1:write`, `core:*:read:self` | | `advisor` | `agent:advisor:chat`, `profile:l1:read`, `profile:l2:read`, `profile:l2:write`, `profile:l3:read`, `core:*:read:assigned` | | `analyst` | `agent:analyst:chat`, `profile:l1:read`, `profile:l2:read`, `profile:l3:read`, `core:*:read:scoped`, `sql:execute:readonly`, `risk:alert:read` | | `risk_officer` | `agent:risk:chat`, `profile:l1:read`, `profile:l2:read`, `profile:l3:read`, `profile:l3:write`, `risk:alert:write`, `risk:suitability:write`, `core:*:read:all` | | `risk_manager` | `risk:alert:read`, `core:*:read:all` | 风控经理:HTTP 台账全量只读,**无处置权、对话线经 chat 层显式拒绝**(PRD 4A.1 / C5 前置) | | `compliance` | `audit:read:all`, `agent:advisor:audit`, `compliance:hit:read` | | `ops` | `agent:advisor:stats`, `audit:read:aggregated` | | `service_risk` | `agent:risk:suitability_check`, `risk:suitability:write`, `profile:l1:read`, `profile:l2:read`, `audit:write` | > 实际权限以 Core RBAC 同步为准;上表为 **Agent 平台最低基线**。 ### 5.4 Agent × 角色 准入矩阵 | Agent (`X-Agent-Type`) | 允许 `token_type` | 允许 `roles` | | --- | --- | --- | | `customer` | `customer` | `customer` | | `advisor` | `staff` | `advisor`, `compliance`, `ops` | | `analyst` | `staff` | `analyst`, `compliance` | | `risk` | `staff`, `service` | `risk_officer`, `service_risk`, `risk_manager` | --- ## 6. 数据层归属校验(第二层) JWT 通过后,**任何涉及 `customer_id` 的读写** 必须执行归属校验。 ### 6.1 校验规则表 | 操作者角色 | 目标资源 | 校验逻辑 | 失败错误码 | | --- | --- | --- | --- | | `customer` | 任意含 `customer_id` 的资源 | `token.customer_id == resource.customer_id` | `AUTH_403_NOT_OWNER` | | `advisor` | 客户画像 / 持仓 / 会话 | `customer_advisor_rel` 存在 `advisor_id=sub AND customer_id=? AND rel_status=active` | `AUTH_403_NOT_ASSIGNED` | | `analyst` | 客户级明细 | 走 **数据权限目录**;无明细权限则 **仅允许聚合 SQL**(`COUNT`/`GROUP BY`,结果 ≥ k-匿名阈值) | `AUTH_403_SCOPE` | | `risk_officer` | 全量客户 | 允许读全部;写仅限 L3 / 预警 / 适当性 | — | | `risk_manager` | 全量客户 | **仅读**预警台账(`risk:alert:read`,无 `risk:alert:write`);对话线(chat)经 `app/api/chat.py` 显式 `deny(AUTH_403_ROLE)` 拒绝,强制走 HTTP 台账 | — | | `compliance` | 审计 / 会话 | 可跨客户读 **审计类表**;读 `agent_message` 需 `audit:read:content` 额外权限 | `AUTH_403_AUDIT_SCOPE` | | 跨 Agent 读会话 | `agent_session` | 角色只能读 **同 agent_type** 会话;合规除外 | `AUTH_403_SESSION_AGENT` | ### 6.2 归属校验伪代码(共用 SDK) ```python def assert_customer_access(ctx: AuthContext, customer_id: str, action: str): if ctx.token_type == "customer": if ctx.sub != customer_id: audit_denied(ctx, "AUTH_403_NOT_OWNER", customer_id) raise Forbidden(...) elif "advisor" in ctx.roles: if not rel_exists(ctx.sub, customer_id, status="active"): audit_denied(ctx, "AUTH_403_NOT_ASSIGNED", customer_id) raise Forbidden(...) elif "analyst" in ctx.roles: if action == "detail" and not ctx.has_perm("core:customer:read:detail"): audit_denied(ctx, "AUTH_403_SCOPE", customer_id) raise Forbidden(...) # aggregate 走 SQL 网关白名单 elif "risk_officer" in ctx.roles: return # 读全开;写由 permission 再判 else: audit_denied(ctx, "AUTH_403_ROLE", customer_id) raise Forbidden(...) ``` ### 6.3 画像 L1 / L2 / L3 RBAC(与数据矩阵对齐) | 资源 | customer | advisor | analyst | risk_officer | compliance | | --- | --- | --- | --- | --- | --- | | L1 读 | 本人 | 名下客户 | 脱敏/聚合 | 全部 | 审计 | | L1 写 | 本人 | — | — | — | — | | L2 读 | — | 名下客户 | 脱敏/聚合 | 全部 | 审计 | | L2 写 | — | 名下客户 | — | — | — | | L3 读 | — | 名下客户 | 脱敏/聚合 | 全部 | 审计 | | L3 写 | — | — | — | ✓ | — | **客户永远不可见 L2/L3**(API 层直接 404,避免泄露存在性)。 --- ## 7. 各 Agent 鉴权要点 ### 7.1 客户财富 Agent | 检查点 | 规则 | | --- | --- | | 入口 | 仅 `token_type=customer` | | 会话 | `agent_session.actor_id` 必须 = `customer_id` | | Core 只读 | 持仓/流水 API 自动注入 `customer_id=ctx.sub` | | Tool | 禁止传入他人 `customer_id`;传入则 `AUTH_403` + 审计 | | 画像 | 只写 L1;读 L3 禁止 | | 适当性 | 只读 `risk_suitability_log` 本人记录 | ### 7.2 代理人助手 Agent | 检查点 | 规则 | | --- | --- | | 入口 | `staff` + `advisor`/`compliance`/`ops` | | 查客户 | 请求体/Tool 中 `customer_id` **必过** `customer_advisor_rel` | | 合规台 A-05 | `compliance` 角色 + `audit:read:all`;可按 `advisor_id` 过滤,不能改数据 | | 草稿 A-03 | `advisor_draft.advisor_id` 必须 = `ctx.sub` | | 外发 | 无 `message:send:customer` 权限;系统级禁止代理人 Agent 直发客户 | ### 7.3 数据分析 Agent | 检查点 | 规则 | | --- | --- | | 入口 | `staff` + `analyst` | | SQL | 仅 `SELECT`;SQL 网关白名单 schema;禁止 `INSERT/UPDATE/DELETE` | | 明细 vs 聚合 | 无明细权限时,拦截含 `customer_id` 单行输出的查询 | | 画像 | L1/L2/L3 **只读**;写请求一律 403 | | 预警 | `risk_alert` 只读;无 `risk:alert:handle` | | 留痕 | 每次查询写 `analytics_query_log`,含 `staff_id=ctx.sub` | ### 7.4 风控监测 Agent | 检查点 | 规则 | | --- | --- | | 人机入口 | `staff` + `risk_officer` | | 交易钩子 R-02 | `service` + `service_risk` + mTLS;不创建会话 | | 写权限 | 可写 `risk_alert`, `customer_profile_l3`, `risk_suitability_log` | | 读权限 | L0/L1/L2 全读;**不可写** L1/L2 | | 阻断 | 仅 R-02 API 可返回 `blocked`;须写 `audit_log` + `risk_suitability_log` | --- ## 8. Tool / API 统一鉴权流程 ```mermaid sequenceDiagram participant Client participant Gateway participant Agent participant AuthSDK participant DB Client->>Gateway: Request + JWT + X-Agent-Type Gateway->>Gateway: 验签 / exp / aud / token_type Gateway->>Gateway: RBAC: role ∈ allowed? Gateway->>Agent: AuthContext (sub, roles, perms, trace_id) Agent->>AuthSDK: check_resource(customer_id, action) AuthSDK->>DB: customer_advisor_rel / self check alt 通过 AuthSDK-->>Agent: OK Agent->>DB: 业务读写 else 拒绝 AuthSDK->>DB: audit_log (auth_denied) AuthSDK-->>Agent: Forbidden Agent-->>Client: 403 + error_code end ``` ### 8.1 AuthContext(注入给各 Agent 服务) ```typescript interface AuthContext { traceId: string; tokenType: 'customer' | 'staff' | 'service'; subjectId: string; // sub roles: string[]; permissions: string[]; tenantId: string; customerId?: string; // token_type=customer advisorId?: string; // 代理人 agentType: 'customer' | 'advisor' | 'analyst' | 'risk'; jti: string; } ``` ### 8.2 共用 SDK 能力清单 | 模块 | 方法 | 说明 | | --- | --- | --- | | `JwtVerifier` | `verify(token)` | 验签、解析、吊销列表检查 | | `RbacGuard` | `can(ctx, permission)` | 权限判断 | | `OwnershipGuard` | `assertCustomer(ctx, customerId)` | 归属校验 | | `ProfileGuard` | `assertProfileAccess(ctx, layer, op)` | L1/L2/L3 | | `SessionGuard` | `assertSession(ctx, sessionId)` | 会话归属 + agent_type | | `AuditService` | `logDenied(ctx, code, detail)` | 越权审计 | --- ## 9. 会话与 Trace 绑定 创建 `agent_session` 时: | 字段 | 赋值规则 | | --- | --- | | `agent_type` | = `X-Agent-Type` | | `actor_id` | = `ctx.sub` | | `actor_role` | = 主角色(`customer`/`advisor`/…) | | `customer_id` | 客户 Agent:`ctx.customer_id`;代理人 Agent:请求指定且通过归属校验 | | `advisor_id` | 代理人 Agent:`ctx.advisor_id` | | `trace_id` | = `X-Trace-Id` | 后续同会话所有 `agent_message` / `agent_tool_call` / `audit_log` 必须带同一 `trace_id`。 **禁止**:使用他人 `session_id` 续聊(`SessionGuard` 校验 `actor_id`)。 --- ## 10. 错误码与 HTTP 状态 | HTTP | error_code | 含义 | 是否写审计 | | --- | --- | --- | --- | | 401 | `AUTH_401_INVALID_TOKEN` | 签名无效 / 过期 / 格式错误 | 是 | | 401 | `AUTH_401_REVOKED` | jti 已吊销 | 是 | | 403 | `AUTH_403_ROLE` | 角色不允许进该 Agent | 是 | | 403 | `AUTH_403_PERMISSION` | 缺少 permission | 是 | | 403 | `AUTH_403_NOT_OWNER` | 客户访问非本人数据 | 是 | | 403 | `AUTH_403_NOT_ASSIGNED` | 代理人访问非名下客户 | 是 | | 403 | `AUTH_403_SCOPE` | 分析员超数据范围 | 是 | | 403 | `AUTH_403_SESSION_AGENT` | 跨 Agent 读会话 | 是 | | 403 | `AUTH_403_AGENT_MISMATCH` | X-Agent-Type 与 Token 不符 | 是 | 响应体统一: ```json { "error_code": "AUTH_403_NOT_ASSIGNED", "message": "无权访问该客户数据", "trace_id": "trace-xxx", "request_id": "req-xxx" } ``` **禁止**在 403 响应中返回「该客户是否存在」等可被枚举的信息(客户 Agent 对外统一话术)。 --- ## 11. 安全补充要求 | 项 | 要求 | | --- | --- | | Token 有效期 | 客户 Access Token ≤ 2h,Refresh 走 IdP;员工 ≤ 8h | | 吊销 | 支持 jti 黑名单(Redis `auth:revoked:{jti}`) | | 限流 | 复用 Redis `guard:rate:{actor_id}:{agent}` | | 高危越权 | 同一 `actor_id` 10 分钟内 ≥ 5 次 `AUTH_403_*` → 临时 `guard:block` | | 日志 | 不在日志打印完整 JWT;只打 `sub`、`jti` 前 8 位 | | 开发环境 | 可用 HS256 + 固定 dev secret,**与生产密钥物理隔离** | --- ## 12. 与现有表结构的关系 | 表 | 鉴权用途 | | --- | --- | | `customer_advisor_rel` | 代理人 **数据归属** 权威来源(Core 同步) | | `agent_session` | 会话 `actor_id` / `advisor_id` / `customer_id` 一致性 | | `audit_log` | 越权、鉴权失败、`event_type=auth_denied` | | `input_guard_log` | 恶意参数、伪造 `customer_id` | --- ## 13. 验收检查清单(F-01 P0) ### 平台 / 网关 - [ ] 四个 Agent 共用同一 `JwtVerifier` 与 `RbacGuard` - [ ] 错误 Token 一律 401,且不进入业务逻辑 - [ ] `X-Agent-Type` 与客户/员工 Token 交叉校验生效 ### 客户财富 Agent - [ ] 客户 A 不能查客户 B 持仓(403 + 审计) - [ ] 客户 Token 不能访问 advisor/analyst/risk 路由 ### 代理人助手 Agent - [ ] 代理人 A 不能查代理人 B 名下客户 - [ ] 合规角色可审计会话,但不能写 L2 画像 ### 数据分析 Agent - [ ] 非授权明细 SQL 被拦截 - [ ] 不能 UPDATE 任何业务表 ### 风控监测 Agent - [ ] 服务账号可调用 R-02,但不能登录人机聊天台 - [ ] 风控不能写 L1/L2 --- ## 14. 实施分工建议 | 负责方 | 交付物 | | --- | --- | | **平台组** | Agent Gateway、Auth SDK、`customer_advisor_rel` 同步 Job | | **IdP / Core** | 角色权限源、JWT 签发与吊销 API | | **各 Agent 组** | 接入 SDK;Tool 入口调用 `OwnershipGuard`;禁止本地 JWT 解析 | | **合规** | 验收 §13 用例 + A-05 审计台权限 | --- ## 15. 关联文档 | 文档 | 关系 | | --- | --- | | [业务场景优先级清单](../../需求拆解/业务场景优先级清单.md) §3.1 F-01 | 需求来源 | | [数据交互矩阵](../../需求拆解/数据交互矩阵.md) §8 | 画像 RBAC | | [Agent风险与合规约束汇总](../../需求拆解/Agent风险与合规约束汇总.md) G-01 | 全局约束 | | [05-多Agent共用底座清单](../项目框架设计/表设计/05-多Agent共用底座清单.md) | 平台 SDK 与 `customer_advisor_rel` | | [02-redis-keys.md](../项目框架设计/表设计/02-redis-keys.md) | 限流 / 封禁 Key | | [01-技术栈与版本.md](./01-技术栈与版本.md) | FastAPI / Redis / 部署环境 | --- ## 附录 A:权限全集(实现时可裁剪) ```text # Agent 入口 agent:customer:chat agent:advisor:chat agent:analyst:chat agent:risk:chat agent:risk:suitability_check agent:advisor:audit agent:advisor:stats # 画像 profile:l1:read profile:l1:write profile:l2:read profile:l2:write profile:l3:read profile:l3:write # Core 只读 core:holding:read:self core:holding:read:assigned core:holding:read:scoped core:holding:read:all core:customer:read:detail # 风控 risk:alert:read risk:alert:write risk:suitability:write risk:suitability:read # 分析与审计 sql:execute:readonly audit:read:all audit:read:aggregated audit:read:content audit:write compliance:hit:read ``` ## 附录 B:Core RBAC 同步建议 | Core 角色 | 映射 Agent roles | 同步频率 | | --- | --- | --- | | 理财师 | `advisor` | 实时 / 5min | | 风控经理 | `risk_officer` | 实时 | | 合规 | `compliance` | 实时 | | 数据分析师 | `analyst` | 实时 | | 运营 | `ops` | 日批 | 员工离职 / 转岗:Core 禁用账号 → IdP 吊销 Refresh Token → Redis 黑名单现有 Access Token `jti`。