跳到主要内容

环境变量与安全管理

环境变量就像保险箱的钥匙——你必须有钥匙才能开箱,但你不会把钥匙贴在保险箱上。

什么是环境变量?

想象你有一把锁和一把钥匙:

  • = 你的应用需要的配置信息(数据库地址、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本地生产覆盖❌ 不提交

优先级(从高到低):

  1. .env.development.local(开发时)
  2. .env.local
  3. .env.development
  4. .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 网站

  1. 登录 vercel.com
  2. 选择你的项目
  3. 点击 SettingsEnvironment Variables
  4. 添加变量:
    • Name: OPENAI_API_KEY
    • Value: sk-你的密钥
    • Environments: 选择 Production / Preview / Development
  5. 点击 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 密钥

  1. supabase.com 创建项目
  2. 进入 SettingsAPI
  3. 复制:
    • Project URL
    • anon public key

2. 获取 OpenAI 密钥

  1. platform.openai.com
  2. 进入 API Keys
  3. 点击 Create new secret key
  4. 复制密钥(只显示一次!)

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 不会自动带过去
  • 密钥泄露了立刻重新生成——不要心存侥幸

安全管理听起来很严肃,但其实就是几个简单的习惯。养成这些习惯,你的项目就不会因为密钥泄露而上新闻。