You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Prisma客户端扩展实现PostgreSQL RLS失效,求排查

RLS未生效排查:跨公司数据访问限制失效问题

我按照Prisma客户端扩展RLS指南实现,但RLS未生效,无法限制跨公司数据访问,麻烦帮忙排查哪里出错了?

相关代码片段

migration.sql(RLS策略定义)

-- Enable Row Level Security
ALTER TABLE "User" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Company" ENABLE ROW LEVEL SECURITY;

-- Force Row Level Security for table owners
ALTER TABLE "User" FORCE ROW LEVEL SECURITY;
ALTER TABLE "Company" FORCE ROW LEVEL SECURITY;

-- Create row security policies
CREATE POLICY tenant_isolation_policy ON "Company" USING ("id" = current_setting('app.current_company_id', TRUE)::uuid);
CREATE POL

schema.prisma(模型与配置)

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["clientExtensions"]
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model Company {
  id    String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  name  String
  users User[]
}

model User {
  id        String  @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
  companyId String  @default(dbgenerated("(current_setting('app.current_company_id'::text))::uuid")) @db.Uuid
  email     String  @unique
  company   Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
}

script.js(测试脚本)

//Set a company_id as current_company_id for RLS Validation
const setUserId = await prisma.$executeRaw`SET app.current_company_id = 'id_of_a_company';`;
// Create a given company with a user
await prisma.company.create({
        data: {
          name: "Company 1",
          users: {
            create: {
              email: "user1@company1.com",
            },
          },
        },
      });
// Try to query users from Company 1 as a user who should have access
    const company1Users = await prisma.user.findMany({
        where: {
        company: {
            name: "Company 1",
        },
        },
    });
    // Try to query users from Company 2 as a user who should not have access
    const company2Users = await prisma.user.findMany({
        where: {
        company: {
            name: "Company 2",
        },
        },
    });

错误分析与修复方案

1. RLS策略严重不完整

  • migration.sql中CREATE POL语句未写完,User表完全没有定义RLS策略,导致User表的RLS仅开启但无执行规则,自然无法限制访问。
  • Company表仅定义了USING(读取限制),缺少WITH CHECK(写入限制),会导致创建/修改Company时不受RLS约束,完全绕开隔离规则。

修复:补全RLS策略

-- Enable Row Level Security
ALTER TABLE "User" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "Company" ENABLE ROW LEVEL SECURITY;

-- Force Row Level Security for table owners
ALTER TABLE "User" FORCE ROW LEVEL SECURITY;
ALTER TABLE "Company" FORCE ROW LEVEL SECURITY;

-- Create row security policies for Company(读写都限制)
CREATE POLICY tenant_isolation_policy ON "Company" 
USING ("id" = current_setting('app.current_company_id', TRUE)::uuid)
WITH CHECK ("id" = current_setting('app.current_company_id', TRUE)::uuid);

-- Create row security policies for User(读写都限制)
CREATE POLICY user_tenant_isolation_policy ON "User"
USING ("companyId" = current_setting('app.current_company_id', TRUE)::uuid)
WITH CHECK ("companyId" = current_setting('app.current_company_id', TRUE)::uuid);

2. 未实现Prisma客户端扩展绑定上下文

仅在schema中开启了clientExtensions预览特性,但没有编写扩展代码自动在每个请求中设置app.current_company_id。测试脚本中用$executeRaw执行SET仅对当前连接生效,而Prisma使用连接池,后续请求可能复用未设置该变量的连接,导致RLS规则失效。

修复:实现客户端扩展

// prisma/client.js
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient().$extends({
  query: {
    $allModels: {
      async $allOperations({ args, query, operation }) {
        // 实际应用中替换为从请求上下文获取当前公司ID的逻辑
        const currentCompanyId = process.env.CURRENT_COMPANY_ID; 
        if (currentCompanyId) {
          await prisma.$executeRaw`SET app.current_company_id = ${currentCompanyId};`;
        }
        return query(args);
      },
    },
  },
})

export default prisma

3. 测试逻辑矛盾

测试脚本中先设置app.current_company_id = 'id_of_a_company',但创建的Company1是自动生成UUID,与设置的ID不一致,再加上Company表缺少写入限制策略,导致创建操作绕开RLS,后续查询的验证逻辑完全混乱。

修复:修正测试脚本

// 指定测试用的公司ID
const testCompanyId = 'your-test-uuid-here';
// 设置当前公司ID
await prisma.$executeRaw`SET app.current_company_id = ${testCompanyId};`;
// 创建指定ID的Company和关联User
await prisma.company.create({
  data: {
    id: testCompanyId,
    name: "Company 1",
    users: {
      create: {
        email: "user1@company1.com",
      },
    },
  },
});

// 后续查询会自动受RLS限制
const company1Users = await prisma.user.findMany(); // 仅返回当前公司的用户
const company2Users = await prisma.user.findMany({
  where: { company: { name: "Company 2" } },
}); // 返回空数组,无权限访问

内容的提问来源于stack exchange,提问作者Saksham arya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 16:17:51