名称: bknd-assign-permissions 描述: 在Bknd中为角色分配权限时使用。涵盖权限语法(简单字符串、扩展格式)、权限效果(允许/拒绝)、带条件的策略、实体特定权限和细粒度访问控制模式。
分配权限
使用简单字符串、带效果的扩展格式和条件策略为角色配置详细权限。
先决条件
- 具有代码优先配置的Bknd项目
- 认证已启用(
auth: { enabled: true }) - 守卫已启用(
guard: { enabled: true }) - 至少定义了一个角色(参见 bknd-create-role)
何时使用UI模式
- 查看当前角色权限
- 快速权限检查
UI步骤: 管理面板 > 认证 > 角色 > 选择角色
注意: 权限分配需要代码模式。UI是只读的。
何时使用代码模式
- 为角色分配权限
- 添加权限效果(允许/拒绝)
- 创建条件策略
- 实体特定权限规则
代码方法
步骤1:简单权限字符串
将基本权限分配为字符串数组:
import { serve } from "bknd/adapter/bun";
import { em, entity, text } from "bknd";
const schema = em({
posts: entity("posts", { title: text().required() }),
});
serve({
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
auth: {
enabled: true,
guard: { enabled: true },
roles: {
editor: {
implicit_allow: false,
permissions: [
"data.entity.read", // 读取任何实体
"data.entity.create", // 在任何实体中创建
"data.entity.update", // 更新任何实体
// 无删除权限
],
},
},
},
},
});
可用权限
| 权限 | 可过滤 | 描述 |
|---|---|---|
data.entity.read |
是 | 读取实体记录 |
data.entity.create |
是 | 创建新记录 |
data.entity.update |
是 | 更新现有记录 |
data.entity.delete |
是 | 删除记录 |
data.database.sync |
否 | 同步数据库架构 |
data.raw.query |
否 | 执行原始SELECT |
data.raw.mutate |
否 | 执行原始INSERT/UPDATE/DELETE |
可过滤 意味着您可以通过策略添加条件/过滤器。
步骤2:扩展权限格式
使用对象实现明确的允许/拒绝效果:
{
roles: {
moderator: {
implicit_allow: false,
permissions: [
{ permission: "data.entity.read", effect: "allow" },
{ permission: "data.entity.update", effect: "allow" },
{ permission: "data.entity.delete", effect: "deny" }, // 明确拒绝
],
},
},
}
权限效果
| 效果 | 描述 |
|---|---|
allow |
授予权限(默认) |
deny |
明确阻止权限 |
拒绝覆盖允许 - 当 implicit_allow: true 但您想阻止特定操作时有用。
步骤3:条件策略
添加策略以实现细粒度控制:
{
roles: {
content_editor: {
implicit_allow: false,
permissions: [
{
permission: "data.entity.read",
effect: "allow",
policies: [
{
description: "仅读取帖子和评论",
condition: { entity: { $in: ["posts", "comments"] } },
effect: "allow",
},
],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [
{
condition: { entity: { $in: ["posts", "comments"] } },
effect: "allow",
},
],
},
],
},
},
}
策略结构
{
description?: string, // 人类可读(可选)
condition?: ObjectQuery, // 策略应用的条件
effect: "allow" | "deny" | "filter",
filter?: ObjectQuery, // 行过滤器(适用于 effect: "filter")
}
策略效果
| 效果 | 目的 |
|---|---|
allow |
条件满足时授予 |
deny |
条件满足时阻止 |
filter |
对结果应用行级过滤器 |
条件运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
$eq |
等于 | { entity: { $eq: "posts" } } |
$ne |
不等于 | { entity: { $ne: "users" } } |
$in |
在数组中 | { entity: { $in: ["posts", "comments"] } } |
$nin |
不在数组中 | { entity: { $nin: ["users", "secrets"] } } |
$gt |
大于 | { age: { $gt: 18 } } |
$gte |
大于或等于 | { level: { $gte: 5 } } |
$lt |
小于 | { count: { $lt: 100 } } |
$lte |
小于或等于 | { priority: { $lte: 3 } } |
步骤4:变量占位符
使用 @variable 引用运行时上下文:
| 占位符 | 描述 |
|---|---|
@user.id |
当前用户的ID |
@user.email |
当前用户的电子邮件 |
@user.role |
当前用户的角色 |
@entity |
当前实体名称 |
@id |
当前记录ID |
示例 - 用户只能更新自己的个人资料:
{
permissions: [
{
permission: "data.entity.update",
effect: "allow",
policies: [
{
condition: { entity: "users", "@id": "@user.id" },
effect: "allow",
},
],
},
],
}
步骤5:实体特定权限
为每个实体授予不同的权限:
{
roles: {
blog_author: {
implicit_allow: false,
permissions: [
// 对帖子的完整CRUD
{
permission: "data.entity.read",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.update",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.delete",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
// 对类别的只读权限
{
permission: "data.entity.read",
effect: "allow",
policies: [{ condition: { entity: "categories" }, effect: "allow" }],
},
],
},
},
}
常见模式
只读角色
{
roles: {
viewer: {
implicit_allow: false,
permissions: ["data.entity.read"],
},
},
}
无删除的CRUD
{
roles: {
contributor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
{ permission: "data.entity.delete", effect: "deny" },
],
},
},
}
具有受限原始访问权限的管理员
{
roles: {
admin: {
implicit_allow: true, // 默认允许所有
permissions: [
// 但拒绝原始数据库访问
{ permission: "data.raw.query", effect: "deny" },
{ permission: "data.raw.mutate", effect: "deny" },
],
},
},
}
多实体角色
{
roles: {
content_manager: {
implicit_allow: false,
permissions: [
// 内容实体:完整CRUD
{
permission: "data.entity.read",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.update",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.delete",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments"] } }, // 无媒体删除
effect: "allow",
}],
},
],
},
},
}
拒绝特定实体
{
roles: {
user: {
implicit_allow: false,
permissions: [
// 可以读取大多数实体
"data.entity.read",
// 但永远不能访问机密实体
{
permission: "data.entity.read",
effect: "deny",
policies: [{
condition: { entity: "secrets" },
effect: "deny",
}],
},
],
},
},
}
创建辅助函数
用于复杂的角色定义:
// helpers/permissions.ts
type EntityPermission = "read" | "create" | "update" | "delete";
function entityPermissions(
entities: string[],
actions: EntityPermission[]
) {
const permMap: Record<EntityPermission, string> = {
read: "data.entity.read",
create: "data.entity.create",
update: "data.entity.update",
delete: "data.entity.delete",
};
return actions.map((action) => ({
permission: permMap[action],
effect: "allow" as const,
policies: [{
condition: { entity: { $in: entities } },
effect: "allow" as const,
}],
}));
}
// 用法
{
roles: {
blog_author: {
implicit_allow: false,
permissions: [
...entityPermissions(["posts", "comments"], ["read", "create", "update"]),
...entityPermissions(["categories", "tags"], ["read"]),
],
},
},
}
验证
测试权限分配:
1. 以具有角色的用户登录:
curl -X POST http://localhost:7654/api/auth/password/login \
-H "Content-Type: application/json" \
-d '{"email": "editor@example.com", "password": "password123"}'
2. 测试允许的权限:
curl http://localhost:7654/api/data/posts \
-H "Authorization: Bearer <token>"
# 应该返回200和数据
3. 测试拒绝的权限:
curl -X DELETE http://localhost:7654/api/data/posts/1 \
-H "Authorization: Bearer <token>"
# 应该返回403禁止访问
4. 测试实体特定权限:
# 如果仅允许帖子/评论:
curl http://localhost:7654/api/data/users \
-H "Authorization: Bearer <token>"
# 如果用户实体不在允许列表中,应该返回403
常见陷阱
权限未生效
问题: 更改权限后,旧行为仍然存在
修复: 重启服务器 - 角色配置在启动时加载:
# 停止并重启
bknd run
拒绝未覆盖
问题: 拒绝效果未阻止访问
修复: 检查策略条件 - 拒绝仅当条件匹配时应用:
// 错误 - 无条件,可能不匹配
{ permission: "data.entity.delete", effect: "deny" }
// 正确 - 在权限级别简单拒绝
{
permissions: [
"data.entity.read",
"data.entity.create",
// 根本不包含删除
],
}
实体条件不匹配
问题: 实体特定权限不起作用
修复: 验证实体名称是否完全匹配:
// 错误 - 实体名称大小写敏感
{ condition: { entity: "Posts" } }
// 正确 - 使用精确的实体名称
{ condition: { entity: "posts" } }
多个策略冲突
问题: 多个策略导致混淆行为
修复: 理解评估顺序 - 第一个匹配的策略获胜:
{
policies: [
// 更具体的先
{ condition: { entity: "secrets" }, effect: "deny" },
// 通用回退最后
{ effect: "allow" },
],
}
变量占位符未解析
问题: @user.id 在过滤器中以字面形式出现
修复: 变量仅适用于 filter 和 condition 字段:
// 正确用法
{
condition: { "@id": "@user.id" }, // 有效
filter: { user_id: "@user.id" }, // 有效
}
注意事项
要做:
- 从最小权限开始,根据需要添加
- 对多个实体使用
$in运算符 - 添加后测试每个权限
- 使用描述性策略描述
- 优先使用明确权限而非
implicit_allow
不要做:
- 向非管理员角色授予
data.raw.*(SQL注入风险) - 将
implicit_allow: true与拒绝策略一起使用(混淆) - 忘记在配置更改后重启服务器
- 不必要地混合简单字符串和扩展格式
- 使用太多嵌套策略过度复杂化
相关技能
- bknd-create-role - 定义新角色
- bknd-row-level-security - 按用户所有权过滤数据
- bknd-protect-endpoint - 保护特定端点
- bknd-public-vs-auth - 配置公共与认证访问
- bknd-setup-auth - 初始化认证系统