1. 页面概览
1.1 是什么
API Key 管理页是 LightFoundry 的开发者门户,面向 SDK 与机器到机器(machine-to-machine)调用场景提供长期凭证(区别于 AIP 登录的短期 JWT)。页面允许已登录用户创建、查看、撤销与删除自己的 API Key:创建时填写名称、Scopes 与过期时间,创建成功后一次性展示明文 Key(lfk_ 前缀 + 32 位随机串),数据库仅存该 Key 的 SHA256 哈希,关闭提示后任何人均无法再次查看明文。
页面同时提供「我的 API Key」列表,逐条展示 Key 的状态(有效 / 已撤销 / 已过期)、过期时间、最近使用时间与绑定的 scopes;支持两种下线方式——撤销(IsActive=false,保留记录供审计)与删除(物理删除,不可恢复)。底部还内置一个「验证 API Key(Demo)」区,输入完整明文 Key 后调用后端公开验证端点,返回该 Key 绑定的 user_id 与 scopes,用于在接入 SDK 之前确认凭证有效性。
从产品定位上看,本页属于「认证与密钥」基础设施(V3-4 阶段交付,对应 TAD-14 §4.4/§5),是外部系统以程序化方式访问 Foundry 本体、语义层与数据能力的入口凭证管理面。
1.2 核心价值
| 维度 | 说明 |
|---|---|
| 长期凭证 | Key 绑定具体用户,供 CI/CD 流水线、数据导出脚本等无人值守场景使用,不依赖登录会话 |
| 哈希存储 | 库中只存 SHA256 哈希(key_hash),库表泄露也无法反推明文 |
| 一次性明文 | 明文仅创建响应返回一次,前端提供「复制」按钮并强提示立即保存 |
| 细粒度 scopes | 每个 Key 可声明一组 scope(如 ontology:read,query:execute),随 Key 存储并在验证时返回 |
| 生命周期管理 | 过期时间、撤销(保留记录)与删除(物理清除)三种下线方式,兼顾审计与应急 |
| 验证闭环 | 内置 verify 端点,SDK / 外部系统调用前可先行校验 Key 有效性与绑定身份 |
1.3 一句话总结
AuthorizeUser 的数据面/动作执行路径会因空角色注入而拒绝。接入建议绑定最小权限账号。2. 访问入口
2.1 路由与菜单
- 路由:
/foundry/api-keys - 路由名称:
FoundryApiKeys - 路由标题:
Foundry API Key - 菜单位置:Foundry 左侧边栏「API Key」(FoundryLayout 菜单项)
- 源码文件:
action/web/src/views/ApiKeysPage.vue(381 行)
2.2 认证与权限
- 需要登录:是(路由
meta.requiresAuth = true,挂 FoundryLayout) - 登录方式:AIP 统一登录,
localStorage.aip_token经client.js请求拦截器自动附加为Authorization: Bearer <token> - 页面本身无角色限制(Foundry 无独立
adminMiddleware,登录即可访问全部 protected 端点) - 归属隔离:列表、撤销、删除均按当前登录用户过滤(
user_id由后端从 JWT 注入),看不到也改不了别人的 Key - 404 排错:若访问
/foundry/api-keys出现 404,先确认 Foundry 后端(端口 18081)已启动、前端路由已注册,且 Vite 代理/api已指向 18081
2.3 端口与 API 前缀
- 后端端口:18081(Foundry)
- 前端 API 前缀:
/api/v1(复用共享client.js,axios 实例baseURL = '/api/v1') - 请求超时:默认 30000ms(30 秒)
- 注意:
client.js同时被 AIP 等产品复用,其头部注释中「代理到后端 18080」的说明是针对 AIP 的旧注记;Foundry 部署下 Vite 代理将/api分流到 18081
3. 界面布局
页面纵向分为 5 个板块(从上到下):
┌──────────────────────────────────────────────────────────────┐ │ ① 页头:标题「API Key 管理」+ 副题「开发者门户 · 长期凭证…」 │ │ ② 操作结果提示条(alert,成功绿 / 失败红,可关闭) │ │ ③ 创建 API Key 卡片(名称 / Scopes / 过期时间 + 两个按钮) │ │ ④ 一次性明文展示卡片(创建成功后出现,含复制与关闭按钮) │ │ ⑤ 我的 API Key 列表卡片(表格 + 撤销 / 删除操作列) │ │ ⑥ 验证 API Key(Demo)卡片(完整 Key 输入 + 验证结果展示) │ └──────────────────────────────────────────────────────────────┘
各板块职责:
- ① 页头:说明本页定位是「开发者门户 · 长期凭证(SDK / 机器到机器调用)」,与登录会话凭证区分。
- ② 提示条:所有操作的成败反馈统一走此区域;
alert.message非空时显示,右侧「关闭」按钮(.link-btn)可手动清空。 - ③ 创建卡片:三列排布(名称 / Scopes / 过期时间),底部「创建 API Key」(主按钮)与「清空」按钮。
- ④ 明文卡片:仅在创建成功返回明文后出现,浅黄色高亮(
plain-key-card),<code>展示完整 Key(user-select: all便于全选),附「复制」与「我已保存,关闭」。 - ⑤ 列表卡片:加载中显示「加载中...」;空列表显示「暂无 API Key,请先创建。」;有数据渲染九列表格。
- ⑥ 验证卡片:输入完整 Key(placeholder
lfk_xxxxxxxx...),点「验证」后在sample-box中展示user_id与 scopes 标签。
4. 交互元素详解
4.1 创建表单
| 元素 | 位置 | 含义 | 必填 / 默认值 | 操作效果与触发调用 |
|---|---|---|---|---|
| 名称 * | 创建卡片第 1 列 | Key 的业务名称(如 CI/CD Pipeline、数据导出脚本) | 必填(required) | 空名提交会被前端拦截并提示「请填写 Key 名称」 |
| Scopes | 创建卡片第 2 列 | 逗号分隔的权限标识(中文逗号亦可),如 ontology:read,query:execute | 非必填,留空则 scopes 为空数组 | 提交时按 /[,,]/ 拆分、去空格、去空项 |
| 过期时间 | 创建卡片第 3 列 | datetime-local 类型输入,超过该时间 Key 自动失效 | 非必填,留空 = 永不过期 | 提交时转 ISO 字符串;有值则 expires_at 非空 |
| 创建 API Key | 按钮 | 提交创建 | — | 调 POST /api/v1/api-keys;成功后展示明文卡片并刷新列表 |
| 清空 | 按钮 | 重置表单 | — | 调用 resetCreateForm() 将三个字段全部置空 |
创建提交的请求体字段名:name(trim 后)、scopes(数组)、expires_at(ISO 字符串或 null)。
4.2 一次性明文展示卡片
| 元素 | 含义 | 操作效果 |
|---|---|---|
<code> 明文 | 创建响应返回的完整 Key(lfk_ 开头) | 展示后即不可再查;样式 user-select: all 便于鼠标全选 |
| 复制 | 一键复制明文到剪贴板 | 调 navigator.clipboard.writeText;成功后提示「明文 Key 已复制到剪贴板」,失败降级提示「复制失败,请手动选中复制」 |
| 我已保存,关闭 | 关闭明文卡片 | 置空 newPlainKey,卡片消失,此后无法再次查看明文 |
若后端未返回明文(旧版本后端),前端提示「创建成功,但未返回明文(请检查后端版本)」(alert-warning)。
4.3 我的 API Key 列表
| 列 | 含义 | 说明 |
|---|---|---|
| ID | Key 记录主键 | 数字自增 |
| 名称 | Key 名称 | 粗体展示 |
| 前缀 | key_prefix | 固定为 lfk_,cell-code 样式 |
| Scopes | 权限标识集合 | 每个 scope 一个 scope-tag 标签;为空显示「全部」 |
| 过期时间 | expires_at | formatTime 格式化(T 替换为空格、截取 19 位),空显示 - |
| 最近使用 | last_used_at | 验证成功时会更新,空显示 - |
| 状态 | 有效 / 已撤销 / 已过期 | 徽标三态:status-enabled(有效)/ status-disabled(已撤销)/ status-expired(已过期) |
| 创建时间 | created_at | 同上格式化 |
| 操作 | 撤销 / 删除 | 见下 |
4.4 撤销与删除
| 按钮 | 出现条件 | 确认文案 | 操作效果与调用 |
|---|---|---|---|
| 撤销 | 仅当 k.is_active === true | 「确定撤销 API Key「{name}」吗?撤销后立即失效(记录保留)。」 | 调 POST /api/v1/api-keys/:id/revoke;成功提示「API Key「{name}」已撤销」并刷新列表 |
| 删除 | 恒显示 | 「确定删除 API Key「{name}」吗?此操作不可恢复。」 | 调 DELETE /api/v1/api-keys/:id;成功提示「API Key「{name}」已删除」并刷新列表 |
两个操作均有 window.confirm 二次确认,且操作期间按钮 busy 置灰防重复提交。后端对「不属于当前用户」的 Key 返回 404 类错误(「API Key 不存在或不属于当前用户」)。
4.5 验证 API Key(Demo)
| 元素 | 含义 | 操作效果 |
|---|---|---|
| 完整 Key 输入框 | 粘贴要验证的完整明文 Key | v-model="verifyKey",placeholder lfk_xxxxxxxx... |
| 验证按钮 | 提交验证 | 空输入时禁用;调 GET /api/v1/api-keys/verify?key=<encodeURIComponent>;成功后 sample-box 展示 user_id 与 scopes 标签,提示「Key 验证通过」 |
验证端点位于公开路由组(无需 JWT),返回 {user_id, scopes, valid:true};前端仅展示 user_id 与 scopes。
5. 后端关联
5.1 API 客户端
- 文件:
action/web/src/api/client.js baseURL: '/api/v1',timeout: 30000,请求头Content-Type: application/json- 请求拦截器:从
localStorage取aip_token,存在则附加Authorization: Bearer <token> - 响应拦截器:401 时清除
aip_token/aip_username并跳转/login(登录页不重复跳转) - 本页未新建独立客户端,直接
import apiClient from '../api/client.js',路径均以/api-keys相对拼接
5.2 端点表(前缀 /api/v1)
| 方法 | 路径 | 请求体 / 参数 | 鉴权 | 超时 | 用途 |
|---|---|---|---|---|---|
| POST | /api-keys | {name, scopes[], expires_at?} | JWT | 30s | 创建 Key,返回一次性明文 |
| GET | /api-keys | — | JWT | 30s | 我的 Key 列表 |
| DELETE | /api-keys/:id | — | JWT | 30s | 物理删除 Key |
| POST | /api-keys/:id/revoke | — | JWT | 30s | 撤销 Key(保留记录) |
| GET | /api-keys/verify?key= | 查询参数 key | 公开 | 30s | 验证 Key,返回 user_id + scopes |
路由注册位置:foundry/server/server.go setupRouter()——RegisterAPIKeyVerifyRoute(api, s) 挂在公开组 api := r.Group("/api/v1");RegisterAPIKeyRoutes(protected, s) 挂在 protected := api.Group("")(走 authMiddleware)。
5.3 响应结构(JSON 示例)
创建成功(HTTP 201):
{ "code": 0, "data": {
"api_key": "lfk_aB3dEfGhIjKlMnOpQrStUvWxYz012345",
"key": { "id": 1, "name": "CI/CD Pipeline", "key_prefix": "lfk_",
"user_id": "550e8400-...", "scopes": ["ontology:read"],
"expires_at": null, "last_used_at": null, "is_active": true,
"created_at": "2026-08-30T08:00:00Z", "updated_at": "..." }
} }
列表(HTTP 200):{ "code": 0, "data": [ { ...同上 key 结构,无 api_key 明文... } ] }
撤销 / 删除:{ "code": 0, "data": { "revoked": true, "id": 1 } } / { "code": 0, "data": { "deleted": true, "id": 1 } }
验证(HTTP 200):{ "code": 0, "data": { "user_id": "...", "scopes": ["ontology:read"], "valid": true } }
错误响应统一为 { "code": "<错误码>", "error": "<描述>" };无效前缀 / 不存在 / 已撤销 / 已过期均返回 401 AUTH_ERROR。
5.4 关联模块
| 后端包 / 文件 | 职责 |
|---|---|
foundry/server/apikey_handlers.go | HTTP handler:创建 / 列表 / 删除 / 撤销 / 验证五端点 |
foundry/apikey/service.go | 业务服务:生成、SHA256 哈希、查询、撤销、删除、验证 |
foundry/apikey/model.go | APIKey 模型,表 api_keys(key_hash 唯一索引) |
foundry/apikey/apikey_test.go | 单元测试(生成 / 验证 / 撤销等) |
foundry/server/core_auth_apikey_v4_test.go | 认证集成测试(Bearer lfk_ 走 API Key 认证) |
5.5 关键机制
- Key 生成:
GenerateKey()生成lfk_+ 32 位随机字符(62 字符表,熵约 190 bit);HashKey()对完整 Key 计算标准 SHA256 十六进制。 - 哈希存储:
api_keys.key_hash唯一索引;ListKeys返回的记录不含key_hash(json:"-"),明文只在创建响应出现一次。 - 验证链路:去空格 → 校验
lfk_前缀 → 按哈希精确查库 → 校验is_active→ 校验expires_at未过 → 更新last_used_at(失败不阻断)→ 返回user_id + scopes。 - 认证器接口:
apikey.Authenticator的Authenticate返回(userID, scopes, err),与 JWT 解析二选一:authMiddleware对Bearer中lfk_前缀的凭证走 API Key 认证,否则回退 JWT。 - scope 语义债(重要):
scopes目前只存储与返回(verify 能查到),authMiddleware不按 scope 过滤端点访问(apiKeyScopeGate()闸门已就绪但默认不挂载)。鉴权口径为双路径:API Key 请求在上下文注入空角色(roles=[]、username="apikey"),故走AuthorizeUser的数据面/动作执行路径(语义检索、对象查询读授权、Action 逐动作 RBAC)默认拒绝;而按「绑定用户 DB 角色」判权的 admin 门禁端点(如 markings 的requireAdmin先查IsUserAdmin(创建者))不受空角色约束,Key 能力=创建者全集。接入建议绑定最小权限账号。详见「已知缺陷」。
6. 核心流程详解
6.1 创建 Key 主流程
- 在「名称 *」输入业务名(必填),可选填 Scopes(逗号分隔)与过期时间。
- 点「创建 API Key」→ 前端校验名称非空 →
busy=true→ 调POST /api/v1/api-keys。 - 后端
CreateKey:校验名称非空、绑定当前用户 →GenerateKey()→HashKey()→ 落库 → 返回{api_key 明文, key 记录}。 - 前端把
data.data.api_key写入newPlainKey,展示黄色明文卡片并提示「API Key 创建成功,请立即复制明文(仅展示一次)」,同时resetCreateForm()清空表单、fetchKeys()刷新列表。 - 用户点「复制」保存明文,或「我已保存,关闭」收起卡片。关闭后明文不可再查。
分支:后端未返回明文时,前端提示「创建成功,但未返回明文(请检查后端版本)」(alert-warning),列表仍刷新。
6.2 撤销流程
- 列表中找到目标 Key(须
is_active === true),点「撤销」。 window.confirm二次确认 → 调POST /api/v1/api-keys/:id/revoke。- 后端
RevokeKey:按id AND user_id更新is_active=false;0 行影响返回「API Key 不存在或不属于当前用户」。 - 成功提示「API Key「{name}」已撤销」,
fetchKeys()刷新——状态徽标变为「已撤销」(灰色),操作列不再显示「撤销」按钮(记录保留)。
6.3 删除流程
- 点「删除」→
window.confirm确认「此操作不可恢复」。 - 调
DELETE /api/v1/api-keys/:id→ 后端物理删除该行。 - 成功提示「API Key「{name}」已删除」,刷新后记录消失。删除不可恢复,撤销更安全。
6.4 验证流程(Demo 与 SDK 前置校验)
- 输入完整明文 Key(
lfk_开头)→ 点「验证」。 - 前端调公开端点
GET /api/v1/api-keys/verify?key=...(无需登录)。 - 后端
ValidateKey校验通过则返回user_id + scopes + valid:true,同时更新last_used_at。 - 前端在
sample-box展示user_id与 scopes 标签,提示「Key 验证通过」。
业务侧(SDK / 外部系统)同样可先调 verify 确认凭证,再以 Authorization: Bearer lfk_... 调业务端点。
7. 权限与安全
- 认证:管理端点(创建 / 列表 / 撤销 / 删除)要求 JWT(
aip_token);验证端点公开。机器调用以Bearer lfk_...经 API Key 认证器鉴权。 - 归属隔离:
ListKeys/RevokeKey/DeleteKey全部按user_id过滤,当前用户无法操作他人的 Key;CreateKey绑定currentUserID(c),不由客户端指定。 - 数据级安全:Key 绑定的是用户身份,其后续访问继承该用户的 RLS/CLS 与对象级可见性;请求上下文注入空角色(
roles=[]),走AuthorizeUser的数据面/动作路径默认拒绝;按绑定用户 DB 角色判权的端点则能力=创建者全集(admin 绑定的 Key 可写)。scopes闸门默认不挂载,接入建议绑定最小权限账号。 - 凭证明文保护:库表只存 SHA256 哈希;列表响应不含哈希与明文;明文一次性返回并提示立即保存。
- 写操作防护:撤销 / 删除前
window.confirm二次确认;操作期间busy置灰,防连点重复提交。
8. 常见问题与排错
8.1 创建成功后没有弹出明文
- 现象:提示「创建成功,但未返回明文(请检查后端版本)」,列表能看到新 Key。
- 原因:后端版本过旧或响应中
data.api_key缺失。 - 排查:检查后端
apikey_handlers.gohandleCreateAPIKey是否返回api_key字段;升级后端后重试。
8.2 验证 Demo 返回 401 / 验证失败
- 现象:输入 Key 点「验证」,提示「验证失败:invalid api key ...」。
- 原因与排查:① Key 前缀不是
lfk_——校验后端返回的api_key原文;② Key 已被撤销——is_active=false,请重新创建;③ Key 已过期——检查expires_at;④ Key 已被删除——删除后不可恢复,需重建;⑤ 粘贴了不完整字符(含空格/换行)——前端会 trim,但确认无多余空白。
8.3 列表加载失败 / 一直转圈
- 现象:「我的 API Key」区域持续显示「加载中...」或提示「加载 API Key 列表失败」。
- 排查:① 打开浏览器 Network 面板看
GET /api/v1/api-keys状态码——401 说明aip_token失效,页面会自动跳转登录;② 确认 Foundry 后端 18081 已启动、Vite 代理/api指向正确;③ 若接口报 5xx,查看后端日志ListKeys查询错误。
8.4 明文复制失败
- 现象:点「复制」提示「复制失败,请手动选中复制」。
- 原因:浏览器剪贴板 API 在非 HTTPS 或受限权限下不可用。
- 处理:按提示手动选中
<code>明文(样式已user-select: all)复制;或改用 HTTPS 环境访问。
8.5 撤销后还想恢复
- 现象:误撤销了一个 Key。
- 说明:撤销仅置
is_active=false,没有反向启用接口。需要恢复只能重新创建一把 Key(明文与旧 Key 不同,下游调用方需更新凭证);物理删除后更是不可恢复。
9. 已知缺陷与边界
| 项目 | 说明 |
|---|---|
| scope 不强制执行(语义债) | scopes 仅存储与返回,authMiddleware 不按 scope 过滤端点访问(apiKeyScopeGate() 闸门已就绪、默认不挂载)。鉴权双路径:请求上下文注入空角色(roles=[]),走 AuthorizeUser 的数据面/动作执行路径默认拒绝;按绑定用户 DB 角色判权的端点 Key 能力=创建者全集(admin 绑定的 Key 可写)——接入建议绑定最小权限账号 |
| 明文不可恢复 | 明文仅在创建响应返回一次;关闭提示 / 刷新页面后无法再次查看,只能重新创建 |
| 撤销不可逆 | 无重新启用接口,撤销后需重建 Key |
| 删除不可恢复 | 物理删除记录,无回收站 |
| 验证端点公开 | GET /api/v1/api-keys/verify 无需登录,可被枚举尝试(仅返回绑定信息,无业务数据泄露,仍建议监控) |
| 单 Key 绑定单用户 | Key 无法同时代表多用户或多角色;多人共用需每人各建 Key |
| 无轮转 / 自动续期 | 过期时间到期后 Key 即失效,系统不会自动续期或通知 |