API Key 只展示一次,根因在存储侧:服务端保存的是密钥的不可逆哈希,而不是明文,所以系统自身也拿不回完整密钥,更不存在”再给你看一次”的接口。这个设计把泄露面压到最小——即使数据库被拖走、管理后台被攻破,攻击者拿到的也是一串哈希,无法还原出能调网关的凭证。企业级 AI 网关创建 API Key 时前端弹出窗口完整展示一次并引导复制,之后列表里只显示掩码,就是这么来的。下面拆解背后的完整链路。
一、为什么不能随时查看完整 Key
很多人第一反应是”我自己的 Key 凭什么不能回看”。核心矛盾在于:回看意味着服务端必须能还原明文,而能还原明文就意味着存了可逆数据,存了可逆数据就意味着任何拿到库的人都能批量复刻全部密钥。密钥等价于密码,业界通行做法是只存哈希、校验时重新哈希比对。网关项目里每个 Key 只对应一个开发者账号,用不可逆算法存储即可,损失”可回看”换取的是整个租户体系的凭证安全。
1.1 明文存储与哈希存储的差异
| 对比项 | 明文存储 | 不可逆哈希存储 |
|---|---|---|
| 数据库泄露后的风险 | 全部密钥可被直接使用 | 哈希不可还原,风险可控 |
| 是否支持回看完整 Key | 支持 | 不支持 |
| 管理后台被攻破 | 凭证全部暴露 | 只能看到掩码 |
| 实现成本 | 低 | 低 |
网关在创建接口里用安全随机源生成密钥,立刻计算哈希入库,明文字符串只存在本次响应的内存里,返回即弃。哈希算法上选带随机盐的版本,相同前缀的 Key 也不会产出相同哈希,进一步消除彩虹表攻击面。网关校验请求头里的 Key 时用常数时间比较,避免时序侧信道泄露比对结果。
二、从创建到展示的完整链路
前端看到的那一次展示,背后是一套严格的时序。按下面顺序执行:
- 前端提交创建请求,携带名称、过期天数、权限范围;
- 服务端用加密安全的随机数生成器产出 32 字节密钥,转成可读字符串;
- 服务端立即计算密钥哈希并入库,数据库只落哈希与掩码;
- 响应体携带完整密钥,并标记这是唯一一次明文返回;
- 前端弹出一次性窗口,展示完整值并提供复制按钮,同时用醒目文案提示保存;
- 关闭弹窗后,Key 列表接口只返回掩码,前端内存中的完整值一并清空。
async function createApiKey(name) {
const res = await fetch('/gateway/api/keys', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, expiresDays: 30 })
});
const { key } = await res.json(); // 仅此一次返回明文
showOnceModal(key);
}
2.1 掩码的生成规则
列表页展示用掩码而不是截断:保留前 8 位和后 4 位,中间用星号填充,既能让用户辨认是哪把 Key,又不会泄出可用的信息。
function maskKey(key) {
return `${key.slice(0, 8)}********${key.slice(-4)}`;
}
三、前端展示层的三个细节
一次性展示的体验直接影响安全策略能否被执行下去。三个细节我们在项目里反复打磨:
- 复制按钮优先于手动选中。完整 Key 是一长串无规律字符,手动框选容易漏字符,用
navigator.clipboard写入剪贴板并给出成功反馈; - 展示窗口不可再唤起。弹窗关闭即销毁,页面刷新后不会从任何本地缓存里恢复完整值,前端也不把完整 Key 写进 DOM 属性或 localStorage;
- 文案明确告知后果。窗口里直接写”此密钥仅显示一次,关闭后无法再次查看”,丢失只能重新生成,避免用户误以为随时能找回。
四、权限范围与生命周期
一次性展示解决的是”拿不到”,权限与过期解决的是”拿到了能用多久”。网关创建 Key 时让用户勾选权限范围,比如只读调用、可管理模型、可创建子 Key,服务端把 scope 写进密钥对应的记录,网关在每次请求鉴权时同步校验。过期策略同样在前端表单里配置,服务端定时扫描并拒绝过期 Key,前端列表用”已过期”标签标识,避免用户拿着失效凭证排查半天。
审计日志覆盖创建、吊销、最后使用三个节点。最后使用时间由网关在每次请求命中时更新,前端在 Key 详情里展示近 7 天调用趋势,异常突增时提示用户检查是否泄露并主动轮换。
浏览器端还有一道防线:渲染任何用户可控内容时统一做文本转义,防止注入类攻击借管理后台页面读取到密钥。剪贴板写入用一次性 Promise,复制完成后立即清理引用,不让完整 Key 在内存里多逗留。
五、Key 丢失后的恢复机制
一次性展示必然伴随”用户没保存”的情况。网关的兜底是提供重新生成(轮换)入口:生成新 Key 的同时吊销旧 Key,旧凭证立即失效。这样既不牺牲安全,又给了用户出路。审计日志里记录每次创建与吊销,配合异常调用监控,密钥泄露后能快速定位影响面。
常见问题(FAQ)
Q1:完整 Key 为什么不能通过忘记密码类似流程找回?
找回需要服务端还原明文,等价于可逆存储,会放大数据库泄露风险。
Q2:掩码会不会被暴力还原?
掩码只保留 12 位字符,剩余部分空间极大,暴力还原不可行。
Q3:丢了 Key 只能重新创建吗?
重新生成即可,旧 Key 立即吊销,不影响账号其他凭证。