一把泄露的 OpenAI Key,一晚上能刷掉几个月工资。这类事故几乎全部源于同一个习惯:把密钥写死在代码里。这篇按"存放—传输—使用—兜底"四段把防线讲清,最后给一张可勾选的上线清单。
第一道防线:密钥只活在环境变量里
硬编码密钥等于把银行卡密码贴在门口。正确姿势是把敏感配置放进 .env / .env.local,并用 .gitignore 挡住:
# .gitignore
.env
.env.local
.env.*
// 错误:const apiKey = 'sk-xxx'
// 正确:
const apiKey = process.env.OPENAI_API_KEY;
Next.js 有个专门的前后端分界规则要记住:NEXT_PUBLIC_ 前缀的变量会被打进前端包,等于公开;真正的密钥一律不带前缀,只在服务端读。部署到 Vercel 这类平台时,还要在控制台配一份同名变量——本地 .env 不会跟着代码走。
第二道防线:浏览器端不存敏感数据
localStorage 会被页面上任何一段 JS 读取,XSS 一旦得手就是一锅端。密码、完整用户资料、支付信息,一个都不能放。临时数据用 sessionStorage 或 HttpOnly Cookie:
res.setHeader('Set-Cookie',
'token=xxx; HttpOnly; Secure; SameSite=Strict; Path=/');
HttpOnly 让 JS 读不到,Secure 强制 HTTPS,SameSite=Strict 压掉 CSRF——三个标志缺一不可。
第三道防线:数据库行级安全
Supabase 这类 BaaS 的默认表权限是"知道地址就能读",必须手动开 RLS:
ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
CREATE POLICY "User can access own data"
ON your_table FOR SELECT USING (auth.uid() = user_id);
没有 RLS 的 Supabase 项目,等于把数据库连接串贴在公网上。
兜底:额度上限是最后的保险丝
前两道防线的目标是"不泄露",但工程上必须假设会泄露。给每个 API Key 配置用量上限,盗刷损失就从"无限"变成"封顶"。原文没有展开的一条增量实践:主 Key 和业务 Key 要分离——前端代理用低额度 Key,后台批处理用高额度 Key,泄露时的爆炸半径完全不同。此外,密钥轮换要有流程:发现泄露后第一步是立刻在平台后台作废重发,而不是先去 Git 历史里删记录(那已经晚了)。
上线前逐项自查
| 检查项 | 判定 |
|---|---|
代码与提交历史里搜不到 sk- 前缀 |
git log -p \| grep sk- |
.env 系列文件全部被 gitignore 且未进远端 |
git ls-files \| grep .env 为空 |
| Vercel/平台控制台变量与本地一致 | 部署后接口正常 |
| Supabase 全部表开启 RLS | 匿名请求返回空集 |
| 浏览器存储无密钥/密码/支付字段 | DevTools Application 面板核对 |
| 每个 Key 都设了额度上限 | 平台用量页确认 |
七项全绿才允许上线。安全这件事没有"差不多"——密钥一旦进过 Git 历史,唯一的正确动作就是作废重发。

评论0