name: bknd-production-config description: 用于准备Bknd应用程序进行生产部署。涵盖安全硬化、环境配置、isProduction标志、JWT设置、Guard启用、CORS、媒体存储和生产检查清单。
配置生产环境
准备并保护您的Bknd应用程序进行生产部署。
先决条件
- 本地测试过的Bknd应用程序
- 数据库已配置(参见
bknd-database-provision) - 选择的托管平台(参见
bknd-deploy-hosting)
何时使用UI模式
- 在管理面板中查看当前配置
- 验证Guard设置是否激活
- 检查认证配置
何时使用代码模式
- 所有生产配置更改
- 设置环境变量
- 配置安全设置
- 设置适配器
代码方法
步骤1:启用生产模式
设置isProduction: true以禁用开发功能:
// bknd.config.ts
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true, // 或 env.NODE_ENV === "production"
}),
};
isProduction: true的作用:
- 禁用模式自动同步(防止意外迁移)
- 隐藏API响应中的详细错误消息
- 禁用管理面板修改(只读)
- 启用更严格的安全默认值
步骤2:配置JWT认证
关键: 在生产中永远不要使用默认或弱的JWT秘密。
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true,
auth: {
jwt: {
secret: env.JWT_SECRET, // 必需,至少32个字符
alg: "HS256", // 或 "HS384"、"HS512"
expires: "7d", // 令牌生命周期
issuer: "my-app", // 可选,标识令牌来源
fields: ["id", "email", "role"], // 令牌中的声明
},
cookie: {
httpOnly: true, // 防止XSS访问
secure: true, // 仅HTTPS
sameSite: "strict", // CSRF保护
expires: 604800, // 7天,单位秒
},
},
}),
};
生成安全秘密:
# Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
# OpenSSL
openssl rand -hex 32
步骤3:启用Guard(授权)
export default {
app: (env) => ({
connection: { url: env.DB_URL },
isProduction: true,
config: {
guard: {
enabled: true, // 强制执行所有权限
},
},
}),
};
未启用Guard时,所有认证用户具有完全访问权限。
步骤4:配置CORS
export default {
app: (env) => ({
// ...
config: {
server: {
cors: {
origin: env.ALLOWED_ORIGINS?.split(",") ?? ["https://myapp.com"],
credentials: true, // 允许cookie
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
},
},
},
}),
};
步骤5:配置媒体存储
在无服务器生产中永远不要使用本地存储。 使用云提供商:
// AWS S3
export default {
app: (env) => ({
// ...
config: {
media: {
enabled: true,
body_max_size: 10 * 1024 * 1024, // 10MB最大上传
adapter: {
type: "s3",
config: {
bucket: env.S3_BUCKET,
region: env.S3_REGION,
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
},
},
},
}),
};
// Cloudflare R2
config: {
media: {
adapter: {
type: "r2",
config: { bucket: env.R2_BUCKET },
},
},
}
// Cloudinary
config: {
media: {
adapter: {
type: "cloudinary",
config: {
cloudName: env.CLOUDINARY_CLOUD,
apiKey: env.CLOUDINARY_KEY,
apiSecret: env.CLOUDINARY_SECRET,
},
},
},
}
完整生产配置
// bknd.config.ts
import type { CliBkndConfig } from "bknd";
import { em, entity, text, relation, enumm } from "bknd";
const schema = em(
{
users: entity("users", {
email: text().required().unique(),
name: text(),
role: enumm(["admin", "user"]).default("user"),
}),
posts: entity("posts", {
title: text().required(),
content: text(),
published: enumm(["draft", "published"]).default("draft"),
}),
},
({ users, posts }) => ({
post_author: relation(posts, users), // posts.author_id -> users
})
);
type Database = (typeof schema)["DB"];
declare module "bknd" {
interface DB extends Database {}
}
export default {
app: (env) => ({
// 数据库
connection: {
url: env.DB_URL,
authToken: env.DB_TOKEN,
},
// 模式
schema,
// 生产模式
isProduction: env.NODE_ENV === "production",
// 认证
auth: {
enabled: true,
jwt: {
secret: env.JWT_SECRET,
alg: "HS256",
expires: "7d",
fields: ["id", "email", "role"],
},
cookie: {
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "strict",
expires: 604800,
},
strategies: {
password: {
enabled: true,
hashing: "bcrypt",
rounds: 12,
minLength: 8,
},
},
allow_register: true,
default_role_register: "user",
},
// 授权
config: {
guard: {
enabled: true,
},
roles: {
admin: {
implicit_allow: true, // 完全访问
},
user: {
implicit_allow: false,
permissions: [
"data.posts.read",
{
permission: "data.posts.create",
effect: "allow",
},
{
permission: "data.posts.update",
effect: "filter",
condition: { author_id: "@user.id" },
},
{
permission: "data.posts.delete",
effect: "filter",
condition: { author_id: "@user.id" },
},
],
},
anonymous: {
implicit_allow: false,
is_default: true, // 未认证用户
permissions: [
{
permission: "data.posts.read",
effect: "filter",
condition: { published: "published" },
},
],
},
},
// 媒体存储
media: {
enabled: true,
body_max_size: 10 * 1024 * 1024,
adapter: {
type: "s3",
config: {
bucket: env.S3_BUCKET,
region: env.S3_REGION,
accessKeyId: env.S3_ACCESS_KEY,
secretAccessKey: env.S3_SECRET_KEY,
},
},
},
// CORS
server: {
cors: {
origin: env.ALLOWED_ORIGINS?.split(",") ?? [],
credentials: true,
},
},
},
}),
} satisfies CliBkndConfig;
环境变量模板
创建.env.production或在您的平台中设置:
# 必需
NODE_ENV=production
DB_URL=libsql://your-db.turso.io
DB_TOKEN=your-turso-token
JWT_SECRET=your-64-char-random-secret-here-generate-with-openssl
# CORS
ALLOWED_ORIGINS=https://myapp.com,https://www.myapp.com
# 媒体存储(S3)
S3_BUCKET=my-bucket
S3_REGION=us-east-1
S3_ACCESS_KEY=AKIA...
S3_SECRET_KEY=secret...
# 或 Cloudinary
CLOUDINARY_CLOUD=my-cloud
CLOUDINARY_KEY=123456
CLOUDINARY_SECRET=secret
# OAuth(如果使用)
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
安全检查清单
认证
- [ ] JWT秘密至少32个字符,随机生成
- [ ] JWT秘密存储在环境变量中,而非代码中
- [ ] Cookie
httpOnly: true设置 - [ ] Cookie
secure: true在生产中(HTTPS) - [ ] Cookie
sameSite: "strict"或"lax" - [ ] 密码哈希使用bcrypt,轮数 >= 10
- [ ] 强制最小密码长度(8+字符)
授权
- [ ] Guard启用(
guard.enabled: true) - [ ] 为匿名用户定义默认角色
- [ ] 管理员角色除非有意,否则不使用
implicit_allow - [ ] 敏感实体有明确权限
- [ ] 行级安全过滤器用户拥有的数据
数据
- [ ]
isProduction: true设置 - [ ] 数据库凭证在环境变量中
- [ ] 生产中无测试/种子数据
- [ ] 配置数据库备份
媒体
- [ ] 配置云存储(非本地文件系统)
- [ ] 存储凭证在环境变量中
- [ ] 存储桶上配置CORS
- [ ] 限制最大上传大小(
body_max_size)
网络
- [ ] CORS来源明确列出(无通配符
*) - [ ] 强制HTTPS(通过平台/代理)
- [ ] 配置API速率限制(如果需要)
平台特定安全
Cloudflare Workers
// 通过wrangler设置秘密
// wrangler secret put JWT_SECRET
// wrangler secret put DB_TOKEN
export default hybrid<CloudflareBkndConfig>({
app: (env) => ({
connection: d1Sqlite({ binding: env.DB }),
isProduction: true,
auth: {
jwt: { secret: env.JWT_SECRET },
cookie: {
httpOnly: true,
secure: true,
sameSite: "strict",
},
},
}),
});
Vercel
# 通过Vercel CLI或仪表板设置
vercel env add JWT_SECRET production
vercel env add DB_URL production
vercel env add DB_TOKEN production
Docker
# docker-compose.yml
services:
bknd:
environment:
- NODE_ENV=production
- JWT_SECRET=${JWT_SECRET} # 来自.env或主机
# 永远不要在docker-compose.yml中直接放置秘密
本地测试生产配置
部署前使用类似生产设置进行测试:
# 创建.env.production.local(git忽略)
NODE_ENV=production
DB_URL=libsql://test-db.turso.io
DB_TOKEN=test-token
JWT_SECRET=test-secret-min-32-characters-here
# 使用生产环境运行
NODE_ENV=production bun run index.ts
# 或源文件
source .env.production.local && bun run index.ts
验证:
- 管理面板为只读(无模式更改)
- API错误不暴露堆栈跟踪
- 认证需要有效JWT
- Guard强制执行权限
常见陷阱
“JWT_SECRET required” 错误
问题: 认证在启动时失败
修复: 确保JWT_SECRET已设置且可访问:
# 检查环境已加载
echo $JWT_SECRET
# Cloudflare:设置秘密
wrangler secret put JWT_SECRET
# Docker:传递环境
docker run -e JWT_SECRET="your-secret" ...
Guard未强制执行权限
问题: 用户可以访问所有内容
修复: 确保Guard已启用:
config: {
guard: {
enabled: true, // 必须为true!
},
}
Cookie未设置(CORS问题)
问题: 认证在Postman中工作,但在浏览器中不工作
修复:
auth: {
cookie: {
sameSite: "lax", // "strict"可能阻止OAuth重定向
secure: true,
},
},
config: {
server: {
cors: {
origin: ["https://your-frontend.com"], // 明确,非"*"
credentials: true,
},
},
}
管理面板允许更改
问题: 模式可以在生产中修改
修复: 设置 isProduction: true:
isProduction: true, // 锁定管理员为只读
暴露详细错误
问题: API返回堆栈跟踪
修复: isProduction: true 隐藏内部错误。同时检查自定义错误处理程序是否暴露细节。
做与不做
做:
- 在生产中设置
isProduction: true - 生成加密安全的JWT秘密(32+字符)
- 启用Guard进行授权
- 使用云存储处理媒体
- 设置明确的CORS来源
- 对所有秘密使用环境变量
- 首先本地测试生产配置
- 启用HTTPS(通过平台/代理)
- 设置cookie
secure: true和httpOnly: true
不做:
- 使用默认或弱的JWT秘密
- 将秘密提交到版本控制
- 使用通配符(
*)CORS来源 - 在生产中保持Guard禁用
- 在无服务器中使用本地文件系统存储
- 暴露详细错误消息
- 跳过安全检查清单
- 使用
sha256密码哈希(使用bcrypt) - 在非管理员角色上设置
implicit_allow: true
相关技能
- bknd-deploy-hosting - 部署到托管平台
- bknd-database-provision - 设置生产数据库
- bknd-env-config - 环境变量设置
- bknd-setup-auth - 认证配置
- bknd-create-role - 定义授权角色
- bknd-storage-config - 媒体存储设置