docs(changelog): #7234 敏感读取判权统一到 token 当前角色(车务明文+供应商域)
changelog-filename-gate / validate (push) Successful in 2s
changelog-filename-gate / validate (push) Successful in 2s
契约不变,仅拒绝口径与审计语义:判权依据由库默认角色改为本次请求 token 当前角色(X-Admin-Role), 撤权最迟 10 分钟生效。TEST 网关实测通过(AC-10/11/12),PR #7272 已合 dev-v3。 backend_status=deployed / gateway_status=verified。 Refs #7234 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
这个提交包含在:
@@ -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<List<OrderTravelerPlainForFleetDTO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
车务派单看板「查看出行人明文」。持 `VEHICLE_MANAGER`/`SUPER_ADMIN` 且其角色拥有 `FLEET_TRAVELER_PLAIN_VIEW` 权限码的车务管理员,查看某订单出行人的明文姓名/证件号/手机号/紧急联系人。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| orderId | path | Long | 是 | 雪花 ID | 订单 ID |
|
||||
| Authorization | header | String | 是 | `Bearer <管理端 token>` | 网关透传 |
|
||||
|
||||
#### 出参 `Result<List<OrderTravelerPlainForFleetDTO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| name | String | 明文姓名(不变) |
|
||||
| idNo | String | 明文证件号(不变) |
|
||||
| phone | String | 明文手机号(不变) |
|
||||
| emergencyContact | String | 紧急联系人(不变) |
|
||||
| emergencyPhone | String | 紧急联系人电话(不变) |
|
||||
| ageAtDeparture | Integer | 按订单出发日计算的年龄(不变) |
|
||||
|
||||
本单不改返回结构,字段与改前完全一致。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
POST /admin/fleet/board/orders/2094610801951203329/travelers/plain
|
||||
Authorization: Bearer <VEHICLE_MANAGER 的 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```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<List<SupplierListItemRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
供应商管理列表查询。此处作为供应商域的代表:域内读/写/授信写/状态写/资源关系维护端点判权口径同步变化,请求/响应/错误码均不变。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| (查询条件) | query | — | 否 | 同改前 | 本单不改查询参数 |
|
||||
| Authorization | header | String | 是 | `Bearer <管理端 token>` | 网关透传 |
|
||||
|
||||
#### 出参 `Result<List<SupplierListItemRespVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| supplierId | String | 供应商 ID(雪花,序列化为字符串,不变) |
|
||||
| supplierNo | String | 供应商编号(不变) |
|
||||
| shortName | String | 供应商简称(不变) |
|
||||
|
||||
本单不改返回结构,字段与改前完全一致。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /admin/supplier/items/list
|
||||
Authorization: Bearer <ADMIN/FINANCE/SUPER_ADMIN 的 token>
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```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 分钟生效即可)
|
||||
在新工单中引用
屏蔽一个用户