Bknd行级安全配置技能Skill bknd-row-level-security

该技能用于在Bknd框架中实现行级安全(RLS),通过过滤策略限制用户对数据的访问,支持多租户隔离、公共/私有记录、实体特定控制等场景。关键词:Bknd、行级安全、RLS、数据访问控制、过滤策略、多租户、后端安全。

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

名称: bknd-row-level-security 描述: 在Bknd中实现行级安全(RLS)时使用。涵盖过滤策略、用户所有权模式、公共/私有记录、实体特定RLS、多租户隔离和数据级访问控制。

行级安全(RLS)

使用过滤策略实现数据级访问控制,以限制用户可以访问的记录。

前提条件

  • 具有代码优先配置的Bknd项目
  • 启用认证(auth: { enabled: true }
  • 启用守卫(guard: { enabled: true }
  • 至少定义一个角色(参见bknd-create-role
  • 具有所有权字段的实体(例如,user_id

何时使用UI模式

  • 查看当前角色策略
  • 快速策略检查

UI步骤: 管理面板 > 认证 > 角色 > 选择角色

注意: RLS配置需要代码模式。UI为只读。

何时使用代码模式

  • 实现行级安全
  • 创建过滤策略
  • 实体特定的数据隔离
  • 多租户模式

代码方法

步骤1:向实体添加所有权字段

确保实体具有跟踪所有权的字段:

import { serve } from "bknd/adapter/bun";
import { em, entity, text, number } from "bknd";

const schema = em({
  posts: entity("posts", {
    title: text().required(),
    content: text(),
    user_id: number().required(),  // 所有权字段
  }),
});

步骤2:基本RLS - 仅自己的记录

用户只能读取自己的记录:

serve({
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
    auth: {
      enabled: true,
      guard: { enabled: true },
      roles: {
        user: {
          implicit_allow: false,
          permissions: [
            {
              permission: "data.entity.read",
              effect: "allow",
              policies: [
                {
                  description: "用户仅读取自己的记录",
                  effect: "filter",
                  filter: { user_id: "@user.id" },
                },
              ],
            },
          ],
        },
      },
    },
  },
});

过滤策略如何工作

组件 目的
effect: "filter" 应用行级过滤(非允许/拒绝)
filter 添加到每个请求的查询条件
@user.id 替换为当前用户ID的变量

当ID为5的用户查询帖子时,过滤器转换:

// 用户查询
api.data.readMany("posts", { where: { status: "published" } });

// 变为(应用RLS过滤器后)
api.data.readMany("posts", { where: { status: "published", user_id: 5 } });

步骤3:完整CRUD与RLS

对所有操作应用RLS:

{
  roles: {
    user: {
      implicit_allow: false,
      permissions: [
        // 读取:自己的记录
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
        // 创建:允许(user_id通过钩子/插件设置)
        { permission: "data.entity.create", effect: "allow" },
        // 更新:自己的记录
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
        // 删除:自己的记录
        {
          permission: "data.entity.delete",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
      ],
    },
  },
}

步骤4:实体特定RLS

每个实体不同的RLS规则:

{
  roles: {
    user: {
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            // 帖子:按作者过滤
            {
              condition: { entity: "posts" },
              effect: "filter",
              filter: { author_id: "@user.id" },
            },
            // 评论:按用户过滤
            {
              condition: { entity: "comments" },
              effect: "filter",
              filter: { user_id: "@user.id" },
            },
            // 类别:无过滤(公共)
            {
              condition: { entity: "categories" },
              effect: "allow",
            },
          ],
        },
      ],
    },
  },
}

步骤5:公共 + 私有记录

用户看到公共记录和自己的私有记录:

{
  permissions: [
    {
      permission: "data.entity.read",
      effect: "allow",
      policies: [
        {
          condition: { entity: "posts" },
          effect: "filter",
          filter: {
            $or: [
              { is_public: true },      // 公共帖子
              { user_id: "@user.id" },  // 自己的帖子
            ],
          },
        },
      ],
    },
  ],
}

步骤6:草稿/已发布模式

作者看到自己的草稿,所有人看到已发布的:

{
  roles: {
    author: {
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            {
              condition: { entity: "posts" },
              effect: "filter",
              filter: {
                $or: [
                  { status: "published" },  // 任何人都可以读取已发布的
                  { author_id: "@user.id" }, // 作者读取自己的草稿
                ],
              },
            },
          ],
        },
      ],
    },
    viewer: {
      is_default: true,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            {
              condition: { entity: "posts" },
              effect: "filter",
              filter: { status: "published" },  // 仅已发布的
            },
          ],
        },
      ],
    },
  },
}

常见RLS模式

多租户隔离

按组织/租户隔离数据:

const schema = em({
  organizations: entity("organizations", {
    name: text().required(),
  }),
  projects: entity("projects", {
    name: text().required(),
    org_id: number().required(),
  }),
  tasks: entity("tasks", {
    title: text().required(),
    org_id: number().required(),
  }),
});

// 假设用户有org_id字段
{
  roles: {
    member: {
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            {
              condition: { entity: { $in: ["projects", "tasks"] } },
              effect: "filter",
              filter: { org_id: "@user.org_id" },
            },
          ],
        },
        {
          permission: "data.entity.create",
          effect: "allow",
          policies: [
            {
              condition: { entity: { $in: ["projects", "tasks"] } },
              effect: "allow",
            },
          ],
        },
      ],
    },
  },
}

基于团队的访问

用户访问属于他们团队的记录:

// 假设用户有team_id字段
{
  roles: {
    team_member: {
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { team_id: "@user.team_id" },
          }],
        },
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { team_id: "@user.team_id" },
          }],
        },
      ],
    },
  },
}

分层访问(经理模式)

经理看到他们下属的数据:

// 经理看到记录,其中:
// - 他们拥有记录,或
// - 记录属于他们管理的人
// 注意:此模式可能需要通过钩子实现自定义逻辑
{
  roles: {
    manager: {
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: {
              $or: [
                { user_id: "@user.id" },
                { manager_id: "@user.id" },
              ],
            },
          }],
        },
      ],
    },
  },
}

匿名读取,认证写入

{
  roles: {
    anonymous: {
      is_default: true,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: { is_public: true },
          }],
        },
      ],
    },
    user: {
      permissions: [
        // 读取:公共 + 自己的
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: {
              $or: [
                { is_public: true },
                { user_id: "@user.id" },
              ],
            },
          }],
        },
        // 创建/更新/删除:仅自己的
        { permission: "data.entity.create", effect: "allow" },
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
        {
          permission: "data.entity.delete",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
      ],
    },
  },
}

管理员绕过

管理员看到一切,用户看到自己的:

{
  roles: {
    admin: {
      implicit_allow: true,  // 不应用RLS过滤器
    },
    user: {
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { user_id: "@user.id" },
          }],
        },
      ],
    },
  },
}

创建时设置用户所有权

RLS过滤器查询结果,但您还需要在创建时设置所有权。

选项1:客户端设置user_id

// 前端代码
const api = new Api({ baseUrl: "http://localhost:7654/api" });
const user = await api.auth.me();

await api.data.createOne("posts", {
  title: "我的帖子",
  user_id: user.id,  // 客户端设置所有权
});

选项2:服务器钩子(推荐)

使用Bknd事件自动设置所有权:

import { serve } from "bknd/adapter/bun";
import { DataRecordMutatingEvent } from "bknd";

serve({
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
    auth: { /* ... */ },
  },
  options: {
    onBuild: async (app) => {
      const events = app.modules.get("events");

      events.on(DataRecordMutatingEvent, async (event) => {
        if (event.data.action === "create") {
          const authModule = app.modules.get("auth");
          const user = await authModule.resolveAuthFromRequest(event.data.ctx?.request);

          if (user && !event.data.record.user_id) {
            event.data.record.user_id = user.id;
          }
        }
      });
    },
  },
});

验证

1. 创建测试用户

# 用户1
curl -X POST http://localhost:7654/api/auth/password/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "password": "pass123"}'

# 用户2
curl -X POST http://localhost:7654/api/auth/password/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "password": "pass123"}'

2. 作为用户1创建记录

# 以user1登录
TOKEN1=$(curl -s -X POST http://localhost:7654/api/auth/password/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user1@test.com", "password": "pass123"}' | jq -r '.token')

# 创建帖子
curl -X POST http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer $TOKEN1" \
  -H "Content-Type: application/json" \
  -d '{"title": "用户1的帖子", "user_id": 1}'

3. 作为用户2验证RLS

# 以user2登录
TOKEN2=$(curl -s -X POST http://localhost:7654/api/auth/password/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user2@test.com", "password": "pass123"}' | jq -r '.token')

# 查询帖子 - 不应看到user1的帖子
curl http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer $TOKEN2"
# 预期:空数组或仅user2的帖子

4. 验证更新RLS

# user2尝试更新user1的帖子 - 应失败或影响0行
curl -X PATCH http://localhost:7654/api/data/posts/1 \
  -H "Authorization: Bearer $TOKEN2" \
  -H "Content-Type: application/json" \
  -d '{"title": "被黑了!"}'
# 预期:404或0受影响(记录被过滤掉)

常见陷阱

过滤器未应用

问题: RLS过滤器未限制数据

修复: 确保守卫已启用:

{
  auth: {
    enabled: true,
    guard: { enabled: true },  // 必需!
  },
}

错误的变量占位符

问题: 使用@id而不是@user.id

修复: 使用正确的占位符:

占位符 含义
@user.id 当前用户的ID
@user.email 当前用户的电子邮件
@id 当前记录ID(非用户)
// 错误 - @id是记录ID,非用户ID
filter: { user_id: "@id" }

// 正确
filter: { user_id: "@user.id" }

缺少实体条件

问题: RLS应用于错误的实体

修复: 为实体特定RLS添加实体条件:

// 错误 - 应用于所有实体
policies: [{
  effect: "filter",
  filter: { user_id: "@user.id" },
}]

// 正确 - 仅帖子实体
policies: [{
  condition: { entity: "posts" },
  effect: "filter",
  filter: { user_id: "@user.id" },
}]

过滤与允许/拒绝混淆

问题: 当需要过滤时使用effect: "allow"

修复: 理解差异:

效果 目的
allow 授予权限(无数据过滤)
deny 完全阻止权限
filter 允许但过滤结果
// 错误 - 允许所有,无过滤
{ effect: "allow", filter: { user_id: "@user.id" } }

// 正确 - 过滤结果
{ effect: "filter", filter: { user_id: "@user.id" } }

创建时未设置所有权

问题: 新记录的user_id为空

修复: 在客户端设置或使用服务器钩子(参见上面的“创建时设置用户所有权”部分)

复杂$or过滤器不工作

问题: $or过滤器返回错误结果

修复: 验证语法:

// 正确的$or语法
filter: {
  $or: [
    { is_public: true },
    { user_id: "@user.id" },
  ],
}

注意事项

应做:

  • 向需要RLS的实体添加所有权字段(user_id
  • 使用effect: "filter"进行行级限制
  • 为实体特定规则添加实体条件
  • 使用多个用户测试以验证隔离
  • 结合RLS与所有权分配钩子

不应做:

  • 混淆@id(记录)与@user.id(用户)
  • 忘记guard: { enabled: true }
  • effect: "allow"filter字段混合(使用effect: "filter"
  • 对具有不同所有权字段的实体应用相同的过滤器
  • 信任客户端设置所有权而不验证

相关技能

  • bknd-create-role - 为RLS定义角色
  • bknd-assign-permissions - 配置角色权限
  • bknd-protect-endpoint - 保护特定端点
  • bknd-public-vs-auth - 公共与认证访问
  • bknd-crud-read - 使用过滤器查询数据