环境变量与安全管理
环境变量就像保险箱的钥匙——你必须有钥匙才能开箱,但你不会把钥匙贴在保险箱上。
什么是环境变量?
想象你有一把锁和一把钥匙:
- 锁 = 你的应用需要的配置信息(数据库地址、API 密钥等)
- 钥匙 = 环境变量里的值
- 保险箱 =
.env文件
环境变量就是存储在代码之外的配置信息。你的代码通过"变量名"来读取它们,但真正的值不会写在代码里。
为什么?
// ❌ 把密钥写在代码里(危险!)
const apiKey = "sk-1234567890abcdef";
// 这段代码推到 GitHub,所有人都能看到你的密钥
// ✅ 用环境变量(安全!)
const apiKey = process.env.OPENAI_API_KEY;
// 密钥存在服务器上,代码里只有变量名
.env 文件格式
.env 文件就是一个纯文本文件,每行一个配置:
# .env 文件
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
OPENAI_API_KEY=sk-1234567890abcdef
API_SECRET=my-super-secret-key
格式规则
# ✅ 正确格式
KEY=value
KEY="value with spaces"
KEY='also works with single quotes'
# ❌ 错误格式
KEY = value # 等号两边不要有空格
KEY="value" # 值有空格才需要引号
注释
# 这是注释
DATABASE_URL=postgresql://... # 这也是注释
.env 文件的几种变体
Next.js 支持多种 .env 文件:
| 文件名 | 用途 | 是否提交到 Git |
|---|---|---|
.env | 默认环境变量 | ❌ 不提交 |
.env.local | 本地覆盖(所有环境) | ❌ 不提交 |
.env.development | 开发环境专用 | ✅ 可以提交 |
.env.development.local | 本地开发覆盖 | ❌ 不提交 |
.env.production | 生产环境专用 | ✅ 可以提交 |
.env.production.local | 本地生产覆盖 | ❌ 不提交 |
优先级(从高到低):
.env.development.local(开发时).env.local.env.development.env
最常用的两个文件
# .env.local —— 本地开发用,包含真实的密钥
DATABASE_URL=postgresql://root:123456@localhost:5432/mydb
OPENAI_API_KEY=sk-real-key-here
# .env.example —— 给别人看的模板,不含真实值
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
OPENAI_API_KEY=your-openai-api-key-here
.gitignore 必须包含 .env
这是最重要的一条规则:绝不能把包含真实密钥的 .env 文件提交到 Git。
# .gitignore
# 环境变量文件(必须忽略)
.env
.env.local
.env.*.local
# 其他常见忽略项
node_modules/
.next/
如果不小心提交了怎么办?
# 1. 从 Git 历史中移除(但文件保留在本地)
git rm --cached .env.local
# 2. 确保 .gitignore 包含它
echo ".env.local" >> .gitignore
# 3. 提交
git commit -m "chore: 移除 .env.local,更新 .gitignore"
# 4. 如果已经推送到远程,密钥可能已经泄露
# 立即去对应平台(Supabase、OpenAI 等)重新生成密钥!
重要提醒:即使你删除了文件,Git 历史里还是能看到。如果密钥已经推送,立刻重新生成密钥是唯一安全的做法。
Next.js 如何处理环境变量
NEXT_PUBLIC_ 前缀
Next.js 对环境变量有一个重要规则:
# 服务器端可用(默认)
DATABASE_URL=postgresql://...
OPENAI_API_KEY=sk-...
# 客户端也可用(必须加 NEXT_PUBLIC_ 前缀)
NEXT_PUBLIC_APP_NAME=橙皮笔记
NEXT_PUBLIC_API_URL=https://api.example.com
为什么这样设计?
// 服务器端代码(API Routes、Server Components)
// 可以访问所有环境变量
const dbUrl = process.env.DATABASE_URL; // ✅ 可以
const apiKey = process.env.OPENAI_API_KEY; // ✅ 可以
// 客户端代码(浏览器里运行的组件)
// 只能访问 NEXT_PUBLIC_ 开头的变量
const appName = process.env.NEXT_PUBLIC_APP_NAME; // ✅ 可以
const dbUrl = process.env.DATABASE_URL; // ❌ undefined
原理:Next.js 在构建时,会把 NEXT_PUBLIC_ 开头的变量硬编码到客户端代码里。所以不要把敏感信息放在 NEXT_PUBLIC_ 变量里。
# ✅ 适合放客户端的
NEXT_PUBLIC_APP_NAME=我的应用
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_MAP_KEY=公开的地图 API key
# ❌ 绝对不能放客户端的
NEXT_PUBLIC_DATABASE_URL=... # 数据库地址暴露!
NEXT_PUBLIC_SECRET_KEY=... # 密钥暴露!
常见的环境变量
# 数据库
DATABASE_URL=postgresql://user:password@host:5432/dbname
REDIS_URL=redis://localhost:6379
# 认证
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-random-secret-string
# 第三方 API
OPENAI_API_KEY=sk-...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# 邮件
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password
# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000
NODE_ENV=development
在 Vercel 上设置环境变量
部署到 Vercel 后,本地的 .env.local 不会自动带过去。需要手动配置:
方法一:通过 Vercel 网站
- 登录 vercel.com
- 选择你的项目
- 点击 Settings → Environment Variables
- 添加变量:
- Name:
OPENAI_API_KEY - Value:
sk-你的密钥 - Environments: 选择 Production / Preview / Development
- Name:
- 点击 Save
方法二:通过 Vercel CLI
# 安装 Vercel CLI
npm install -g vercel
# 添加环境变量
vercel env add OPENAI_API_KEY
# 会提示你选择环境(Production / Preview / Development)
# 然后输入值
# 查看所有环境变量
vercel env ls
# 拉取环境变量到本地
vercel env pull .env.local
方法三:批量导入
创建一个 .env 文件,然后一次性导入:
vercel env pull .env.production
# 或者在 Vercel 网站点击 "Import .env"
安全管理最佳实践
1. 不同环境用不同的密钥
# 开发环境用测试密钥
STRIPE_SECRET_KEY=sk_test_... # Stripe 测试密钥
# 生产环境用正式密钥
STRIPE_SECRET_KEY=sk_live_... # Stripe 正式密钥
2. 定期轮换密钥
每隔几个月更换一次密钥,特别是怀疑泄露时。
3. 使用 .env.example 作为模板
# .env.example(提交到 Git)
# 复制此文件为 .env.local 并填入真实值
cp .env.example .env.local
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
OPENAI_API_KEY=your-openai-api-key
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key
4. 生成安全的随机密钥
# 用 Node.js 生成随机字符串
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# 用 OpenSSL
openssl rand -hex 32
常见错误和安全风险
❌ 错误 1:把 .env 提交到 Git
# 检查是否有敏感文件被追踪
git status
# 如果 .env.local 出现在列表中,立刻停止!
# 移除追踪
git rm --cached .env.local
❌ 错误 2:在客户端代码中使用敏感变量
// ❌ 危险!这个值会被打包到浏览器代码里
const apiKey = process.env.NEXT_PUBLIC_API_KEY;
// 用户查看网页源码就能看到
// ✅ 正确!通过 API Route 中转
// app/api/chat/route.ts
export async function POST(req: Request) {
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY, // 只在服务器端
});
// ...
}
❌ 错误 3:在代码中硬编码密钥
// ❌ 永远不要这样做
const db = new Database('postgresql://root:123456@localhost/db');
// ✅ 用环境变量
const db = new Database(process.env.DATABASE_URL);
❌ 错误 4:在截图/录屏中暴露密钥
演示项目时注意:
- 打开
.env.local文件前先关掉 - 终端里的环境变量不要截到
- Vercel 设置页面不要录进去
实战:配置 Supabase + OpenAI 密钥
来走一遍完整的流程:
1. 获取 Supabase 密钥
- 去 supabase.com 创建项目
- 进入 Settings → API
- 复制:
- Project URL
- anon public key
2. 获取 OpenAI 密钥
- 去 platform.openai.com
- 进入 API Keys
- 点击 Create new secret key
- 复制密钥(只显示一次!)
3. 配置本地环境
# 复制模板
cp .env.example .env.local
# 编辑 .env.local
# .env.local
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://abcdefghij.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
# OpenAI(服务器端使用,不加 NEXT_PUBLIC_)
OPENAI_API_KEY=sk-1234567890abcdef
# 应用配置
NEXT_PUBLIC_APP_URL=http://localhost:3000
4. 在代码中使用
// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
export const supabase = createClient(supabaseUrl, supabaseKey);
// app/api/chat/route.ts
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function POST(req: Request) {
const { message } = await req.json();
const completion = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: message }],
});
return Response.json(completion.choices[0].message);
}
5. 部署到 Vercel
# 方式一:CLI
vercel env add NEXT_PUBLIC_SUPABASE_URL
vercel env add NEXT_PUBLIC_SUPABASE_ANON_KEY
vercel env add OPENAI_API_KEY
# 方式二:网站设置
# Settings → Environment Variables → 逐个添加
6. 验证
# 本地启动
npm run dev
# 在代码中测试环境变量是否正确
# app/api/test/route.ts
export async function GET() {
return Response.json({
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL ? '✅ 已配置' : '❌ 未配置',
openaiKey: process.env.OPENAI_API_KEY ? '✅ 已配置' : '❌ 未配置',
});
}
访问 http://localhost:3000/api/test,确认两个都显示 ✅。
小结
- 环境变量 = 代码之外的配置——密钥不进代码
- .env.local 不提交 Git——这是铁律
- NEXT_PUBLIC_ 开头的才会暴露给浏览器——敏感信息不加这个前缀
- 用 .env.example 给别人看模板——不含真实值
- 部署时在 Vercel 手动配置——本地的 .env 不会自动带过去
- 密钥泄露了立刻重新生成——不要心存侥幸
安全管理听起来很严肃,但其实就是几个简单的习惯。养成这些习惯,你的项目就不会因为密钥泄露而上新闻。