Files
lingniu-vehicle-ingest/vehicle-data-platform/docs/customer-scope-api-enforcement-matrix.md
2026-07-16 17:00:33 +08:00

11 KiB
Raw Blame History

车辆数据中台客户 Scope API 覆盖矩阵

更新时间2026-07-16

状态:本地客户主体与车辆/时间 Scope 已实施OneOS 联邦身份仍按后续接口契约接入。

1. 当前结论

车辆中台已支持本地 admin/customer 账号、菜单权限、车辆 VIN Scope 和授权有效时间。认证中间件把 subjectId/userType/customerRef/tenantRef/authProvider 以及当前车辆授权注入 Principal;车辆、监控、轨迹、 历史、里程和导出入口均在服务端校验主体与时间边界。客户不能通过 Query、Header 或 Body 自行覆盖 customer ID。

OneOS 身份尚不能直接映射为本地 viewer。后续只有在 OneOS 提供签名、受众、身份版本和客户关联等稳定接口后, 才能映射为同一强类型 Principal在此之前继续使用车辆中台本地客户账号不读取或修改 OneOS 业务逻辑。

2026-07-14 已只读核验 ECS 当前运行状态:

  • AUTH_MODE=enforce
  • 使用 AUTH_TOKENS_JSON,未使用单一 AUTH_TOKEN
  • /opt/lingniu-vehicle-platform/env/platform.env 权限为 0600 root:root
  • lingniu-vehicle-platform 服务已启用且正在运行。

该配置负责内部静态 token客户级隔离由平台账号、会话、菜单和车辆授权表共同提供。

2. 独立发现:高德服务端接口绕过平台鉴权

app.NewServer 当前先把平台 API 包在 withAPIAuth 中,之后又在外层注册 withAMapReverseGeocodeAPI。所以 /api/map/reverse-geocode 在进入鉴权中间件前就会被处理;只要配置了 AMAP_API_KEY,匿名请求即可消耗服务端高德额度。

整改要求:

  • 将逆地理编码路由放回鉴权之后;
  • 对客户和内部主体分别限流,并限制经纬度格式和并发;
  • 不记录完整 access token
  • app-config.js 可继续公开,但只能暴露高德 Web JS 公钥和代理路径,不暴露服务端 API Key
  • 高德安全代理因浏览器 SDK 需要可访问,应在边缘层配置域名来源、速率和响应缓存,不能把它视为客户身份接口。

3. 主体与权限模型

Principal 至少扩展为:

name
role                  # 仅内部功能角色
subject_type          # employee | service | customer
subject_id            # OneOS sys_user.user_id
customer_id           # 仅 customer 必填
tenant_id
identity_version
capabilities          # vehicle:read, history:read, export:create ...

约束:

  • 内部静态 token 只能生成 employee/service 主体;
  • 客户主体只能来自 OneOS 签名、短时且 aud=vehicle-data-platform 的声明;
  • 浏览器 Query、Body、Cookie 或普通转发头中的 customerId 均无效;
  • 客户权限不能通过 roleRank(viewer) 复用内部 viewer 权限,需要独立 capability 判断;
  • 未知 subject_type、缺失 customer ID、签名异常或身份版本失效全部拒绝。

4. QueryScope 机制

所有 Store 查询显式接收 QueryScope,避免新增方法时忘记从 context.Context 读取 Scope

mode: unrestricted | customer_current | customer_period
customer_id
active_version
valid_from / valid_to  # 历史持有期查询

unrestricted 只能由已验证的内部 employee/service 主体创建。空客户 Scope 表示“零车辆”,绝不能解释为不限制。

MySQL 当前数据查询应 JOIN

business_scope_state(id=1)
business_customer_vehicle_scope(
  source_version = active_version,
  customer_id = principal.customer_id,
  vin = 业务表.vin
)

同时要求:

  • last_success_at 距当前时间不超过 10 分钟;
  • active version 存在且快照计数完整;
  • 快照缺失或过期返回 503 BUSINESS_SCOPE_UNAVAILABLE,而不是空列表或全量数据;
  • 快照新鲜但该客户确实没有车辆时返回空列表;
  • 单车越权统一返回 404避免泄露 VIN 是否存在。

TDengine 无法直接 JOIN MySQL Scope单车查询先在 MySQL 做授权判断;多车查询从同一 active version 读取授权 VIN 集合,设置数量上限后再生成参数化过滤。不能先从 TDengine 取全量再由前端过滤。

5. API 覆盖矩阵

5.1 客户首期允许,但必须按当前 Scope 过滤

API 组 代表路由 必须实施的控制
全局监控 /api/v2/monitor/summary/map 车辆、点位、聚合、告警数和所有汇总均从同一客户 Scope 计算;禁止混用全局 DashboardSummary
车辆列表与解析 /api/vehicles/resolve/detail VIN、车牌、终端号解析必须先限定 Scope越权关键字表现为不存在
最新状态 /api/realtime/vehicles/locations 列表、total、分页和筛选均在 SQL 层过滤
车辆只读档案 GET /api/v2/vehicles/{vin}/profile 先校验 VIN只返回门户需要的非敏感字段
最新遥测 /api/v2/vehicles/{vin}/telemetry/latest 路径 VIN 校验 Scope原始协议帧字段按白名单输出
轨迹 /api/v2/tracks 车牌/VIN/终端解析、轨迹点、停留点和统计都使用同一授权 VIN
历史查询 /api/history/locations/api/v2/history/query/series 首期只允许当前车辆且 eventAt 不早于 scope_start_at;不能读取交付前数据
里程 /api/mileage/summary/daily/api/v2/statistics/mileage 列表与汇总按 VIN 和授权时间过滤total 不能泄露全局数量
在线统计 /api/statistics/online-summary/online-vehicles 分母、在线数、离线数、趋势和明细均限定客户车辆
告警只读 /api/v2/alerts/summary/events/events/{id} 列表 JOIN event.vin Scope详情按 ID+Scope 查询,越权返回 404
指标元数据 /api/v2/metrics/api/v2/history/metrics 可返回全局字段定义,但过滤内部专用/敏感指标
逆地理编码 /api/map/reverse-geocode 必须鉴权和限流;不依赖客户参数,不返回其他车辆信息

5.2 客户首期明确禁止

API 组 路由 原因
平台总览 /api/dashboard/summary 包含全局车辆、帧量、Kafka/服务健康信息
接入诊断 /api/vehicles/coverage*/api/vehicle-service* 包含协议覆盖、终端、原始帧和数据质量诊断
原始帧 /api/history/raw-frames* 包含协议证据和内部字段;首期使用白名单遥测/历史 API 代替
质量中心 /api/quality/*、旧 /api/alert-events/* 别名 平台级质量问题,不是客户业务告警
运维 /api/ops/* 数据源、容量、Kafka、Redis 和服务健康仅内部可见
接入管理 /api/v2/access/* 身份绑定、阈值和未解析终端均为内部运维能力
车辆档案写入 PUT /api/v2/vehicles/{vin}/profile/vehicle-profiles/sync 只允许内部管理员/同步服务
告警规则/处置 /api/v2/alerts/rules*events/{id}/actions 首期客户只读,不能修改平台规则或全局事件状态
平台通知 /api/v2/alerts/notifications* 当前通知没有客户 owner读取与已读操作均可能跨客户

禁止应在服务端 capability 层返回 403不能只隐藏前端菜单。

5.3 导出 owner、异步 Scope 与审计(已实施)

release export-owner-scope-20260716165737 已完成:

  • Create/List/Download 全部接收 context 和 Principal
  • Job 固化 owner ID、账号、角色/用户类型、认证来源、客户/租户、已解析 VIN 和逐车时间范围;
  • 创建时同步校验每个 VIN 和历史授权时间;异步开始、每 5,000 行及下载前重新校验客户账号与车辆授权;
  • 非管理员列表只返回当前 owner越权或猜测下载 ID 返回 404管理员保留审计视角
  • 客户停用、车辆授权撤销或授权区间变化会停止未完成任务并拒绝历史文件下载;
  • CSV 文件使用 0640任务索引原子持久化CSV 元数据包含创建账号、角色、客户、车辆 Scope、查询条件与生成时间
  • Web 使用带 Bearer Token 的 Blob 下载,不再通过无 Authorization 的直接链接;
  • 服务重启后 owner、Scope 和完成文件仍可复核,执行中任务安全失败并要求重新创建。

自动化已覆盖两个互斥客户、管理员审计、IDOR、撤权停止和持久化元数据。生产真实导出在重启前后文件 SHA-256 一致,伪造 ID 返回 404且无 .part 文件残留。

6. 当前 Scope 与历史 Scope 的边界

已实现的 business_customer_vehicle_scope 只保存当前有效车辆。它足以保护实时监控,但不能完整实现“客户还车后 仍可查看持有期间历史”。

首期安全策略:

  • 只允许查询当前仍在 Scope 中的车辆;
  • 历史起点不得早于当前记录的 scope_start_at
  • 车辆还车并从当前 Scope 移除后,历史访问也随即关闭。

若产品确认需要持有期历史,再新增版本化区间投影:

customer_id, vin, contract_id, valid_from, valid_to, source_version

历史查询必须满足 VIN 和 valid_from <= event_at < valid_to。不能仅凭“客户曾经拥有过这辆车”开放该 VIN 的全部历史。

7. 必须覆盖的越权测试

准备客户 A、客户 B、内部 viewer、内部 operator、内部 admin 和 service 六类主体,至少覆盖:

  1. A 在列表、关键字解析、路径 VIN、POST body、分页游标中请求 B 的车辆;
  2. A 直接访问 B 的告警事件 ID、导出 ID 和下载 URL
  3. A 使用车牌或终端号绕过 VIN 校验;
  4. A 的空 Scope 不返回全量Scope 缺失/过期返回 503
  5. 快照切换期间所有查询只使用一个 active version不混合新旧结果
  6. total、summary、地图聚合数量和在线率不泄露全局或 B 的数量;
  7. A 只能读取交付后的历史点,不能读取交付前记录;
  8. 车辆从 A 还车并交付给 B 后A 的实时访问立即 404B 只能读取自身授权时间段;
  9. 客户主体访问运维、接入、质量、写档案、规则和处置接口均为 403
  10. 匿名逆地理编码失败;认证请求达到限额后返回 429
  11. 客户 token 的 customer ID 不能被 Query/Header/Body 覆盖;
  12. 服务重启后导出 owner、Scope 和下载授权仍保持,不能退化成全局任务。

8. 推荐实施顺序

  1. 扩展 Principal 和 OneOS JWT 校验,但暂不开放客户路由;
  2. 引入强类型 QueryScope先改车辆解析与单车详情
  3. 改所有列表、汇总、地图、统计和 TDengine 查询;
  4. 改告警详情 IDOR、逆地理编码鉴权限流
  5. 导出 owner 与异步 Scope已完成
  6. 双客户导出自动化与生产管理员实车导出:已完成;完整客户 API 矩阵继续补齐;
  7. 本地测试客户可按菜单和车辆授权启用OneOS 联邦客户入口保持关闭,等待正式身份接口。