name: rls-模式 description: 数据库操作的行级安全模式。在编写Prisma/数据库代码、创建访问数据的API路由或实现webhook时使用。通过withUserContext、withAdminContext、withSystemContext帮助器强制执行。永远不要使用直接的prisma调用。
RLS模式技能
目的
为所有数据库操作强制执行行级安全(RLS)模式。这个技能确保数据隔离,并在数据库层面防止跨用户数据访问。
何时应用此技能
在以下情况调用此技能:
- 编写任何Prisma数据库查询时
- 创建或修改访问数据库的API路由时
- 实现写入数据库的webhook处理器时
- 处理用户数据、支付、订阅或注册时
- 访问管理员专用表(争议、webhook_events)时
关键规则
永远不要这样做
// ❌ 禁止 - 直接的Prisma调用会绕过RLS
const user = await prisma.user.findUnique({ where: { user_id } });
// ❌ 禁止 - 未设置上下文
const payments = await prisma.payments.findMany();
ESLint将阻止直接的Prisma调用。 请查看eslint.config.mjs以了解执行规则。
总是这样做
import {
withUserContext,
withAdminContext,
withSystemContext,
} from "@/lib/rls-context";
// ✅ 正确 - 用户操作使用用户上下文
const user = await withUserContext(prisma, userId, async (client) => {
return client.user.findUnique({ where: { user_id: userId } });
});
// ✅ 正确 - 管理员操作使用管理员上下文
const webhooks = await withAdminContext(prisma, userId, async (client) => {
return client.webhook_events.findMany();
});
// ✅ 正确 - webhooks/后台任务使用系统上下文
const event = await withSystemContext(prisma, "webhook", async (client) => {
return client.webhook_events.create({ data: eventData });
});
上下文帮助器参考
withUserContext(prisma, userId, callback)
用于:所有面向用户的操作
- 用户配置文件访问
- 支付历史
- 订阅管理
- 课程注册
const payments = await withUserContext(prisma, userId, async (client) => {
return client.payments.findMany({ where: { user_id: userId } });
});
withAdminContext(prisma, userId, callback)
用于:管理员专用操作(需要在user_roles表中具有管理员角色)
- 查看所有webhook事件
- 管理争议
- 访问支付失败
const disputes = await withAdminContext(prisma, adminUserId, async (client) => {
return client.disputes.findMany();
});
withSystemContext(prisma, contextType, callback)
用于:Webhooks和后台任务
- Stripe webhook处理器
- Clerk webhook处理器
- 后台任务处理
// Stripe webhook处理器
await withSystemContext(prisma, "webhook", async (client) => {
await client.payments.create({ data: paymentData });
});
管理员页面:强制动态渲染
关键:使用RLS查询的管理员页面必须强制运行时渲染:
// app/admin/some-page/page.tsx
import { withAdminContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
// 必须 - RLS上下文在构建时不可用
export const dynamic = "force-dynamic";
async function getAdminData() {
return await withAdminContext(prisma, userId, async (client) => {
return client.someTable.findMany();
});
}
没有export const dynamic = 'force-dynamic',Next.js会尝试在构建时预渲染,导致“权限被拒绝”错误。
受保护的表
用户数据表(用户隔离)
| 表 | 策略类型 | 访问权限 |
|---|---|---|
user |
用户隔离 | 仅自己数据 |
payments |
用户隔离 | 仅自己支付 |
subscriptions |
用户隔离 | 仅自己订阅 |
invoices |
用户隔离 | 仅自己发票 |
course_enrollment |
用户隔离 | 仅自己注册 |
管理员/系统表(基于角色)
| 表 | 策略类型 | 访问权限 |
|---|---|---|
webhook_events |
管理员+系统 | 仅管理员和webhooks |
disputes |
仅管理员 | 仅管理员 |
payment_failures |
仅管理员 | 仅管理员 |
trial_notifications |
管理员+系统 | 仅管理员和系统 |
测试要求
始终使用{{PROJECT}}_app_user角色测试(而不是{{PROJECT}}_user超级用户):
# 基本RLS功能测试
node scripts/test-rls-phase3-simple.js
# 全面安全验证
cat scripts/rls-phase4-final-validation.sql | \
docker exec -i {{PROJECT_NAME}}-postgres-1 psql -U {{PROJECT}}_app_user -d {{PROJECT}}_dev
常见模式
使用用户上下文的API路由
// app/api/user/payments/route.ts
import { NextResponse } from "next/server";
import { requireAuth } from "@/lib/auth";
import { withUserContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
export async function GET() {
const { userId } = await requireAuth();
const payments = await withUserContext(prisma, userId, async (client) => {
return client.payments.findMany({
where: { user_id: userId },
orderBy: { created_at: "desc" },
});
});
return NextResponse.json(payments);
}
使用系统上下文的Webhook处理器
// app/api/webhooks/stripe/route.ts
import { withSystemContext } from "@/lib/rls-context";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
// 首先验证webhook签名...
await withSystemContext(prisma, "webhook", async (client) => {
await client.webhook_events.create({
data: {
event_type: event.type,
payload: event.data,
processed_at: new Date(),
},
});
});
return new Response("OK", { status: 200 });
}
权威参考
- 实施指南:
docs/database/RLS_IMPLEMENTATION_GUIDE.md - 策略目录:
docs/database/RLS_POLICY_CATALOG.md - 迁移SOP:
docs/database/RLS_DATABASE_MIGRATION_SOP.md - ESLint规则:
eslint.config.mjs(直接Prisma调用执行) - RLS上下文:
lib/rls-context.ts