RLS模式技能Skill rls-patterns

这个技能用于实施数据库行级安全(RLS)模式,确保多用户环境下的数据隔离和安全访问。通过withUserContext、withAdminContext和withSystemContext等上下文帮助器,规范数据库查询操作,防止数据泄露和越权访问。适用于基于Prisma的后端开发、API设计、webhook处理等场景,提升应用安全性和数据隐私保护。关键词:行级安全、RLS、数据库安全、Prisma、数据隔离、API开发、webhook安全、角色控制。

后端开发 0 次安装 0 次浏览 更新于 3/15/2026

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
  • 迁移SOPdocs/database/RLS_DATABASE_MIGRATION_SOP.md
  • ESLint规则eslint.config.mjs(直接Prisma调用执行)
  • RLS上下文lib/rls-context.ts