diff --git a/changelogs-v2/2026-09/07_7234_敏感读取判权统一到token当前角色-修改接口-管理后台.md b/changelogs-v2/2026-09/07_7234_敏感读取判权统一到token当前角色-修改接口-管理后台.md new file mode 100644 index 00000000..7d10b5e2 --- /dev/null +++ b/changelogs-v2/2026-09/07_7234_敏感读取判权统一到token当前角色-修改接口-管理后台.md @@ -0,0 +1,282 @@ +--- +schema: "hl-changelog/v2" +ticket: "7234" +title: "车务出行人明文与供应商域判权口径统一到 token 当前角色(契约不变,仅拒绝口径与审计语义)" +consumer: "admin" +author: "jw(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "mmg" +frontend_ref: "" +target_release: "" +verified_at: "2026-09-07" +status_note: "PR #7272 合入 dev-v3。请求/响应/错误码全不变,前端无需改代码;本条只告知拒绝语义与审计口径:判权依据由库默认角色改为本次请求 token 的当前角色(X-Admin-Role),撤权最迟 10 分钟生效(角色权限码缓存 TTL)。车务明文拒绝仍写审计 DENIED+PERMISSION_DENIED,权限服务不可用写 DENIED+PERMISSION_CHECK_FAILED(两者区分供排查)。" +updated_at: "2026-09-07" +base: "dev-v3" +--- + +# 车务/供应商: 敏感读取判权统一到 token 当前角色 + +> **服务**: hl-fleet-service (8086/8186) · hl-resource-service · hl-order-service-v3 · hl-user-service +> **PR**: #7272 +> **Issue**: #7234 +> **日期**: 2026-09-07 +> **影响范围**: 管理后台「车务派单看板·查看出行人明文」与「供应商域读写」两条链路的鉴权判定口径 + +--- + +## ⚠️ 关键变化 + +**请求、响应、错误码全部不变,前端不改代码。** 唯一变化是「谁有权限」的判定依据: +由**数据库默认角色**(`admin_user.current_role_id`)改为**本次请求 token 的当前角色**(网关注入的 `X-Admin-Role`)。 + +对正常账号无可见变化——正常登录时 token 角色与库角色一致。差异只在两者分叉时出现: +短信登录会把 token 角色临时置成 `CUSTOMIZER` 却不改库里的默认角色,改后这类会话一律按 `CUSTOMIZER` 判权,不再借用库角色的权限码。 + +**撤权最迟 10 分钟生效**:角色权限码走 Redis 缓存(`admin_permissions:v2:role:{roleKey}`,TTL 10 分钟)。撤掉某角色的权限码关联后,最多 10 分钟后该角色被拒。 + +--- + +## 一、背景 + +出行人明文(姓名/证件号/手机号/紧急联系人)是平台敏感度最高的数据。链路上已有调用方白名单、token 与 adminId 一致性校验、二次权限校验、访问审计四道闸,但权限那道判的是**库角色**:一旦 token 角色与库角色分叉,闸门放行与否取决于一个与本次会话无关的字段。 + +这不是「当前公开入口已可越权读明文」——车务入口在网关层已被 `VEHICLE_MANAGER`/`SUPER_ADMIN` 门禁挡住,供应商守卫也先校验 token 角色集合。本单是**口径加固**:把「谁在操作」的唯一事实来源固定为 token 角色,消除库角色与 token 角色分叉时静默放权的可能,并补上 order-v3 内部二次校验缺失的角色绑定(不接受调用方自报角色)。与 #7210 复审返工同一口径。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|---|---|---|---|---| +| 1 | 车务查看出行人明文 | POST | `/admin/fleet/board/orders/:orderId/travelers/plain` | 修改接口 | 契约不变;无权限判定由库角色改按 token 当前角色 | +| 2 | 供应商列表(供应商域读写代表) | GET | `/admin/supplier/items/list` | 修改接口 | 契约不变;供应商域读/写/授信/状态/资源关系全部由库角色改按 token 当前角色,角色集合前置校验不变 | + +--- + +## 三、接口详情 + +### 1. 车务查看出行人明文 `POST /admin/fleet/board/orders/:orderId/travelers/plain` + +**VO**: `无请求体` → `Result>` + +#### 使用场景 + +车务派单看板「查看出行人明文」。持 `VEHICLE_MANAGER`/`SUPER_ADMIN` 且其角色拥有 `FLEET_TRAVELER_PLAIN_VIEW` 权限码的车务管理员,查看某订单出行人的明文姓名/证件号/手机号/紧急联系人。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| orderId | path | Long | 是 | 雪花 ID | 订单 ID | +| Authorization | header | String | 是 | `Bearer <管理端 token>` | 网关透传 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|---|---|---| +| name | String | 明文姓名(不变) | +| idNo | String | 明文证件号(不变) | +| phone | String | 明文手机号(不变) | +| emergencyContact | String | 紧急联系人(不变) | +| emergencyPhone | String | 紧急联系人电话(不变) | +| ageAtDeparture | Integer | 按订单出发日计算的年龄(不变) | + +本单不改返回结构,字段与改前完全一致。 + +#### 请求示例 + +```json +POST /admin/fleet/board/orders/2094610801951203329/travelers/plain +Authorization: Bearer +``` + +#### 响应示例 + +```json +{ + "code": 200, + "success": true, + "data": [ + {"travelerId": "...", "name": "张三", "idNo": "...", "phone": "...", "emergencyContact": "...", "ageAtDeparture": 10} + ] +} +``` + +成功时 `fleet_sensitive_access_log` 落一条 `SUCCESS`(`row_count` = 返回条数)。 + +#### 空数据 / 降级响应 + +该订单无出行人时返回空数组。权限服务不可用一律 fail-closed(不返回明文)。 + +#### 错误响应 + +```json +{"code": 605039, "success": false, "message": "无权查看出行人明文信息", "data": null} +``` + +| 码 | 触发 | 审计落库 | +|---|---|---| +| 403(网关) | 当前角色非 `VEHICLE_MANAGER`/`SUPER_ADMIN` | 请求未到达 fleet,**不写审计** | +| 605039 | 到达 fleet 后:当前角色无 `FLEET_TRAVELER_PLAIN_VIEW` 权限码 | `DENIED` + `PERMISSION_DENIED` | +| 605039 | 权限服务不可用(Feign 降级) | `DENIED` + `PERMISSION_CHECK_FAILED`(与上一行区分,供排查故障 vs 无权限) | + +#### 业务边界 + +- **判权口径变化**:无权限判定由 `hasPermission(adminId, FLEET_TRAVELER_PLAIN_VIEW)`(库角色)改为 `roleHasPermission(当前角色, FLEET_TRAVELER_PLAIN_VIEW)`(token 角色)。 +- **审计语义不变**:拒绝仍写 `DENIED`+`PERMISSION_DENIED`;`FAILED` 仍只用于 order 服务调用失败分支。新增区分:权限服务降级写 `DENIED`+`PERMISSION_CHECK_FAILED`。 +- **内部边界加固**:order-v3 侧二次校验改为从被校验的 token 上取角色判权,请求体声明的角色只做一致性比对,不一致即拒——不接受调用方自报角色。 + +--- + +### 2. 供应商列表(供应商域读写代表) `GET /admin/supplier/items/list` + +**VO**: `SupplierListQueryReqVO` → `Result>` + +#### 使用场景 + +供应商管理列表查询。此处作为供应商域的代表:域内读/写/授信写/状态写/资源关系维护端点判权口径同步变化,请求/响应/错误码均不变。 + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|---|---|---|---|---|---| +| (查询条件) | query | — | 否 | 同改前 | 本单不改查询参数 | +| Authorization | header | String | 是 | `Bearer <管理端 token>` | 网关透传 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|---|---|---| +| supplierId | String | 供应商 ID(雪花,序列化为字符串,不变) | +| supplierNo | String | 供应商编号(不变) | +| shortName | String | 供应商简称(不变) | + +本单不改返回结构,字段与改前完全一致。 + +#### 请求示例 + +```json +GET /admin/supplier/items/list +Authorization: Bearer +``` + +#### 响应示例 + +```json +{"code": 200, "success": true, "data": [ { "supplierId": "...", "shortName": "..." } ]} +``` + +#### 空数据 / 降级响应 + +无命中返回空数组。权限服务不可用一律按无权限 fail-closed(拒绝)。 + +#### 错误响应 + +```json +{"code": 403, "success": false, "message": "无权访问供应商数据", "data": null} +``` + +| 码 | 触发 | +|---|---| +| 403 | 当前 token 角色不在读集合(`READ_ROLES`=ADMIN/FINANCE/SUPER_ADMIN)内 | +| 403 | 角色在集合内但该角色无对应权限码(读);写端点按其既有 `395002`/状态码拒 | + +#### 业务边界 + +- **判权口径变化**:域内私有判权由 `hasPermission(adminId, code)`(库角色)改为 `roleHasPermission(role, code)`(token 角色)。 +- **角色集合前置校验不变**:`READ_ROLES`/`WRITE_ROLES`/`STATUS_WRITE_ROLES`/`RESOURCE_RELATION_*` 仍先按 token 角色集合挡一道,对正常账号无可见变化。 + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误理解 + +- ✅ 判权只认本次请求 token 的当前角色(`X-Admin-Role`)。多角色账号必须先切到有权限的角色再操作。 +- ❌ 不要以为「库里默认角色有权限就一定能读」——短信登录等临时切角色的会话按 token 角色判,不借库角色的码。 +- ✅ 撤销某角色的权限码后,最多 10 分钟生效(角色权限码缓存 TTL)。 + +--- + +## 五、数据库行为 + +- 车务明文入口:只对 `fleet_sensitive_access_log` **追加**一条审计(成功 `SUCCESS`/拒绝 `DENIED`/失败 `FAILED`),无其他业务表写入。判权读的是 user-service 的角色权限码缓存(Redis,TTL 10 分钟),非本服务库表。 +- 供应商域读端点:无写库;判权同样读 user-service 角色权限码。 +- 全链路无表结构变更、无 Flyway。 + +--- + +## 六、边界行为 + +- 车务明文入口:网关角色门禁(403,不写审计)在前,服务内权限码判定(605039,写审计)在后,口径都是 token 角色。 +- 供应商域:角色集合前置校验(403)在前,权限码判定在后,口径都是 token 角色。 +- 权限服务不可用:车务侧写 `DENIED`+`PERMISSION_CHECK_FAILED`(区分于无权限);供应商侧按无权限拒(其域无区分故障/无权限的审计语义)。 + +--- + +## 六.6、修改前后对比 + +| 维度 | 改前 | 改后 | +|---|---|---| +| 车务明文判权依据 | 库默认角色 `admin_user.current_role_id`(`hasPermission(adminId, code)`) | 本次 token 当前角色 `X-Admin-Role`(`roleHasPermission(role, code)`) | +| 供应商域判权依据 | 同上,按 adminId 落库角色 | 按 token 当前角色 | +| order-v3 内部二次校验 | 只绑定 adminId,角色可由调用方自报 | 角色从被校验 token 上取,请求体声明只做一致性比对,不一致即拒 | +| 权限服务不可用时车务审计 | `DENIED`+`PERMISSION_DENIED` | 新增区分:`DENIED`+`PERMISSION_CHECK_FAILED` | +| 请求 / 响应 / 错误码 | — | **完全不变** | + +## 六.7、影响评估 + +- **前端**:无需改代码。正常账号(token 角色=库角色)行为不变;只需知悉拒绝语义与「撤权最迟 10 分钟生效」。 +- **正常账号**:无可见变化。 +- **临时切角色会话**(如短信登录临时 `CUSTOMIZER`):按 token 角色判权,不再借用库角色权限码——这是本单的目的。 +- **回滚**:纯判权口径与内部端点变更,无数据迁移,回滚 PR 即可;但需按部署顺序(user-service 先上)。 + +--- + +## 七、不影响范围 + +- 请求参数、响应结构、错误码:全部不变。 +- 网关路由与 `/admin/fleet/**` 角色门禁:不变。 +- 脱敏返回结构、敏感访问审计落库链路:不变(只读,不改写入语义)。 +- Flyway / 表结构:无变更。 +- 服务间内部端点(`/internal/user/admin/access-token-context` 新增替代 `validate-access-token`)属服务间调用,不面向前端。 + +--- + +## 八、测试环境已验证 + +TEST 网关 + 真实鉴权实测(2026-09-07,PR #7272 分支部署 user→order-v3→fleet→resource): + +### 车务明文(AC-10/AC-11) +- 临时 `CUSTOMIZER` token 调明文入口 → 网关 `403`,`fleet_sensitive_access_log` 计数不变(未到 fleet)。 +- 真实 `VEHICLE_MANAGER`(持 `FLEET_TRAVELER_PLAIN_VIEW`)登录 token 调同入口 → 返回明文(3 条),审计落 `SUCCESS`(row_count=3)。 +- 撤掉 `VEHICLE_MANAGER` 的 `FLEET_TRAVELER_PLAIN_VIEW` 关联并等角色缓存过期后 → 同账号同角色被拒 `605039`,审计落 `DENIED`+`PERMISSION_DENIED`;恢复关联后放行。 + +### 供应商域(AC-12) +- 同一 adminId(库角色 `VEHICLE_MANAGER`,无 `supplier:list`),token 角色 `ADMIN`/`FINANCE` → 200 出数,`CUSTOMIZER`/`VEHICLE_MANAGER` → 403。判权按 token 角色而非库角色(否则四种都应被拒)。 + +### 单测与门禁 +- user/order-v3/fleet/resource 本单新增改动单测全绿;fleet `spotless:check` 干净。含 AC-4 自报角色绕过用例、AC-5 实经 fallback 工厂用例。 + +--- + +## 十、相关文档 + +- 工单 #7234;同口径前序 #7210 复审返工。 + +--- + +## 关联 / 联系人 + +### 链接 + +- Issue: https://git.1814.love:8443/wx/HL/issues/7234 +- PR: https://git.1814.love:8443/wx/HL/pulls/7272 + +### 联系人 + +- 后端: jw +- 前端: mmg(无需改代码;知悉拒绝语义与撤权 10 分钟生效即可)