Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

provider:'api' 数据源裸 fetch 不带 Authorization:自定义端点与原生请求鉴权双轨,cookie 验签失效即 401(原生 bearer 无恙) #2725

Closed

Description

@baozhoutao

现象

视图 data.provider: 'api' 指向的自定义端点(例:项目仓库 os-tianshun-ehr 甘特图的 GET /api/gantt/tree)在 dev 服务重启后恒返回 401,而同一浏览器会话里所有平台原生 /api/v1/* 请求全部正常。用户侧表现为「只有甘特图页面炸,其他页面都好好的」。

根因:provider:'api' 的请求不带会话凭证(Authorization),鉴权凭证与原生请求不同轨

  1. console/原生数据链路的每个请求都带 Authorization: Bearer <token>(查库校验,与 cookie 签名无关)。
  2. provider:'api' 走 ApiDataSource(packages/core/src/adapters/ApiDataSource.ts)——裸 globalThis.fetch,只有浏览器自动附带的同源 cookie,没有 Authorization 头。
  3. 服务端自定义端点用 auth 服务 getSession(headers) 鉴权时,收到的唯一凭证是 cookie,只能走 cookie HMAC 验签这条路。一旦 cookie 验签失败(最典型:dev 未设 OS_AUTH_SECRET,plugin-auth 兜底密钥 'dev-secret-'+Date.now() 每次重启轮换,旧 cookie 签名全部失效),api 数据源的请求就 401,而带 bearer 的原生请求毫发无损。

对照实验(15.1.1 / 16.0.0-rc.0 均复现;坏签名 cookie 等价于「重启后浏览器里的旧 cookie」):

请求 凭证 结果
/api/gantt/tree(自定义端点) 有效 cookie 200
/api/gantt/tree 坏签名 cookie 401
/api/gantt/tree bearer token 200 ← 服务端 getSession 本来就认 bearer
/api/v1/data/...(原生) 坏签名 cookie 401
/api/v1/data/... bearer token 200 ← console 实际走的通道

第三行说明服务端不是瓶颈:只要请求带上宿主已有的 bearer token,自定义端点鉴权与原生完全一致。缺口在发送端没把凭证接上。

代码落点

管线本来就预留了注入口,只是没人接线:

  • ApiDataSource(packages/core/src/adapters/ApiDataSource.ts):构造参数支持 fetch / defaultHeaders ✅
  • resolveDataSource(packages/core/src/adapters/resolveDataSource.ts):ResolveDataSourceOptions 会把 fetch/defaultHeaders 透传给 ApiDataSource ✅
  • 但调用侧全部没传:
    • packages/plugin-gantt/src/ObjectGantt.tsx(~L462):resolveDataSource(dataConfig, dataSource ?? null) —— 第三参缺省;
    • packages/react/src/hooks/useViewData.ts(L105):adapterOptions 依赖使用方显式传入,SchemaRendererContext 里没有任何「宿主认证 fetch」可默认取用;
    • 宿主(console)渲染视图时也没有把自己带 token 的 fetch 注入进来。

建议修复方向

在 SchemaRendererContext 增加可选的宿主级认证请求能力(如 apiFetch 或 getAuthHeaders),resolveDataSource 的调用方(useViewData、ObjectGantt 等)在 provider:'api' 时默认取用;console 宿主接线为自己带 Authorization 的 fetch。这样:

  • provider:'api' 与原生请求的鉴权行为一致(同一 token、同一存活期);
  • 自定义端点无需为「cookie 验签失效」做任何兜底;
  • 向后兼容:上下文没提供时维持现状(裸 fetch + cookie)。

影响面

所有用 provider:'api' 自定义端点的视图(甘特组合端点、报表类聚合端点等)。dev 环境重启即触发;生产环境若 cookie 过期/轮换而 token 尚在,同样会出现「原生接口好好的、api 数据源 401」的不一致。

(应用侧已用「dev 脚本固定 OS_AUTH_SECRET」规避 dev 重启场景——os-tianshun-ehr PR#497——但凭证双轨的不一致仍建议平台归一。)

Activity

  1. baozhoutao commented on Jul 20, 2026

    @baozhoutao
    ContributorAuthor

    定性确认:遗漏(bug),不是故意设计

    对照代码逐条查证后,结论是 发送端接线遗漏,issue 的根因分析成立:

    1. 注入口从第一天就是为宿主认证设计的,但从未接线。 ApiDataSource 诞生的 commit(3856bdd4e)就带 fetch / defaultHeaders 构造参数,resolveDataSource 也原样透传;但全仓库 grep 下来没有任何调用方传过这两个参数——唯一生产调用点 ObjectGantt.tsx 第三参缺省,useViewData 的 adapterOptions 无任何使用者。设计了插座、没插插头。
    2. 宿主已有现成的正确原语,却只接给了原生适配器。 createAuthenticatedFetch()(packages/auth/src/createAuthenticatedFetch.ts)就是干这事的:附 Authorization: Bearer + X-Tenant-ID + Accept-Language。console 在 AdapterProvider.tsx 把它注入了 ObjectStackAdapter(原生链路),ApiDataSource 这条链路被漏掉。若「api 数据源不带凭证」是刻意行为,不会出现同一宿主两条链路一条接了一条没接。
    3. 无任何 ADR / 文档声明这是刻意行为。 docs/adr/ 与 plugin 文档中 provider:'api' 示例对鉴权只字未提。

    影响面比 issue 描述再多两项

    裸 fetch 不止丢 Authorization,还丢:

    • X-Tenant-ID —— 多租户环境下 provider:'api' 端点连租户路由头都没有;
    • Accept-Language —— 服务端本地化同样双轨。

    修复注意点(同源守卫)

    provider:'api' 的 URL 可指向任意第三方外部 API,库层默认裸 fetch 是安全的(盲附 bearer 会泄漏平台 token)。但现有 createAuthenticatedFetch 的判断是 /\/api\//i.test(url),对 https://third-party.com/api/x 这类跨域绝对 URL 也会附 token——接给 ApiDataSource 时必须加一道同源(或平台 baseUrl 前缀)守卫,否则修 401 的同时引入 token 外泄面。

    修复方向(即将开工)

    按 issue 建议落地:SchemaRendererContext 增加可选宿主级认证 fetch(apiFetch),useViewData / ObjectGantt 在 provider:'api' 时默认取用,console 宿主接线为带同源守卫的认证 fetch;上下文未提供时维持现状(裸 fetch + cookie),向后兼容。

  2. self-assigned this
    on Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions