Bknd公共与认证访问配置Skill bknd-public-vs-auth

此技能用于在Bknd后端框架中配置公共与认证访问控制,包括设置匿名角色、管理未认证数据访问、定义公共/私有实体模式、实现混合访问模式,以及保护敏感实体同时暴露公共实体。关键词:Bknd, 访问控制, 认证, 公共访问, 后端开发, 权限管理, 数据安全, API配置。

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

名称: bknd-public-vs-auth 描述: 用于在Bknd中配置公共与认证访问。涵盖匿名角色设置、未认证数据访问、公共/私有实体模式、混合访问模式,以及保护敏感实体同时暴露公共实体。

公共与认证访问

配置哪些数据和端点是公开可访问的,哪些需要认证。

先决条件

  • 具有代码优先配置的Bknd项目
  • 认证启用(auth: { enabled: true }
  • 守卫启用(guard: { enabled: true }
  • 对角色的基本理解(参见bknd-create-role

何时使用UI模式

  • 查看当前角色配置
  • 检查权限分配

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

注意: 访问配置需要代码模式。

何时使用代码模式

  • 为公共访问设置匿名/默认角色
  • 配置实体特定的访问规则
  • 创建混合公共/私有数据模式
  • 构建封闭(需要认证)系统

核心概念:默认角色

Bknd使用默认角色来确定未认证用户可以访问的内容:

用户发起请求 → 有令牌吗? → 是 → 使用用户的角色
                              → 否  → 使用默认角色(is_default: true)
                                    → 没有默认? → 访问被拒绝

代码方法

步骤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: {
        // 公共角色 - 任何人都可以读取
        anonymous: {
          is_default: true,
          implicit_allow: false,
          permissions: ["data.entity.read"],
        },
        // 认证用户可以创建/更新
        user: {
          implicit_allow: false,
          permissions: [
            "data.entity.read",
            "data.entity.create",
            "data.entity.update",
          ],
        },
      },
    },
  },
});

结果:

  • GET /api/data/posts - 无需认证即可工作
  • POST /api/data/posts - 需要认证
  • PATCH /api/data/posts/1 - 需要认证

步骤2:完全私有(需要认证)

所有访问都需要认证:

{
  auth: {
    enabled: true,
    guard: { enabled: true },
    allow_register: true,
    default_role_register: "user",
    roles: {
      admin: { implicit_allow: true },
      user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",
          "data.entity.create",
          "data.entity.update",
        ],
      },
      // 没有默认角色 - 未认证用户无法访问
    },
  },
}

结果: 所有 /api/data/* 端点在无认证时返回403。

步骤3:实体特定公共访问

使一些实体公开,其他私有:

{
  auth: {
    enabled: true,
    guard: { enabled: true },
    roles: {
      anonymous: {
        is_default: true,
        implicit_allow: false,
        permissions: [
          // 仅帖子是公共的
          {
            permission: "data.entity.read",
            effect: "allow",
            policies: [{
              condition: { entity: "posts" },
              effect: "allow",
            }],
          },
        ],
      },
      user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",   // 读取所有实体
          "data.entity.create",
          "data.entity.update",
        ],
      },
    },
  },
}

结果:

  • GET /api/data/posts - 公共
  • GET /api/data/users - 需要认证
  • GET /api/data/comments - 需要认证

步骤4:多个公共实体

暴露多个实体公开:

{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: { $in: ["posts", "categories", "tags"] } },
            effect: "allow",
          }],
        },
      ],
    },
  },
}

步骤5:带有过滤器的公共记录

仅使已发布/公共记录可访问:

{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [
            // 帖子:仅已发布
            {
              condition: { entity: "posts" },
              effect: "filter",
              filter: { status: "published" },
            },
            // 产品:仅可见
            {
              condition: { entity: "products" },
              effect: "filter",
              filter: { visible: true },
            },
          ],
        },
      ],
    },
  },
}

结果: 匿名用户仅看到过滤记录;认证用户看到所有。

步骤6:混合公共/所有者访问

公共可以读取已发布;所有者可以读取自己的草稿:

{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: [
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: { status: "published" },
          }],
        },
      ]
    },
    user: {
      implicit_allow: false,
      permissions: [
        // 读取:已发布或自己的帖子
        {
          permission: "data.entity.read",
          effect: "allow",
          policies: [{
            condition: { entity: "posts" },
            effect: "filter",
            filter: {
              $or: [
                { status: "published" },
                { author_id: "@user.id" },
              ],
            },
          }],
        },
        // 允许创建
        "data.entity.create",
        // 仅更新自己的
        {
          permission: "data.entity.update",
          effect: "allow",
          policies: [{
            effect: "filter",
            filter: { author_id: "@user.id" },
          }],
        },
      ],
    },
  },
}

步骤7:仅邀请系统

没有公共访问,没有自助注册:

{
  auth: {
    enabled: true,
    guard: { enabled: true },
    allow_register: false,  // 禁用自助注册
    roles: {
      admin: { implicit_allow: true },
      member: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",
          "data.entity.create",
          "data.entity.update",
        ],
      },
      // 没有默认角色
    },
  },
  options: {
    seed: async (ctx) => {
      // 管理员手动创建用户
      await ctx.app.module.auth.createUser({
        email: "admin@company.com",
        password: "admin-password",
        role: "admin",
      });
    },
  },
}

步骤8:公共读取、认证写入的API

常见REST API模式:

{
  roles: {
    anonymous: {
      is_default: true,
      implicit_allow: false,
      permissions: ["data.entity.read"],  // 读取任何内容
    },
    api_user: {
      implicit_allow: false,
      permissions: [
        "data.entity.read",
        "data.entity.create",
        "data.entity.update",
        "data.entity.delete",
      ],
    },
  },
}

完整配置示例

博客平台

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

const schema = em(
  {
    posts: entity("posts", {
      title: text().required(),
      content: text(),
      published: boolean().default(false),
    }),
    comments: entity("comments", {
      body: text().required(),
      approved: boolean().default(false),
    }),
    users: entity("users", {}),
  },
  ({ posts, comments, users }) => [
    relation(posts, "author").manyToOne(users),
    relation(comments, "post").manyToOne(posts),
    relation(comments, "user").manyToOne(users),
  ]
);

serve({
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
    auth: {
      enabled: true,
      guard: { enabled: true },
      allow_register: true,
      default_role_register: "commenter",
      roles: {
        // 公共:读取已发布帖子 + 已批准评论
        anonymous: {
          is_default: true,
          implicit_allow: false,
          permissions: [
            {
              permission: "data.entity.read",
              effect: "allow",
              policies: [
                {
                  condition: { entity: "posts" },
                  effect: "filter",
                  filter: { published: true },
                },
                {
                  condition: { entity: "comments" },
                  effect: "filter",
                  filter: { approved: true },
                },
              ],
            },
          ],
        },
        // 注册用户:读取所有,创建评论
        commenter: {
          implicit_allow: false,
          permissions: [
            "data.entity.read",
            {
              permission: "data.entity.create",
              effect: "allow",
              policies: [{
                condition: { entity: "comments" },
                effect: "allow",
              }],
            },
          ],
        },
        // 作者:完全帖子访问,管理自己的评论
        author: {
          implicit_allow: false,
          permissions: [
            "data.entity.read",
            {
              permission: "data.entity.create",
              effect: "allow",
              policies: [{
                condition: { entity: { $in: ["posts", "comments"] } },
                effect: "allow",
              }],
            },
            {
              permission: "data.entity.update",
              effect: "allow",
              policies: [{
                condition: { entity: "posts" },
                effect: "filter",
                filter: { author_id: "@user.id" },
              }],
            },
          ],
        },
        // 管理员:所有内容
        admin: { implicit_allow: true },
      },
    },
  },
});

SaaS 应用

{
  auth: {
    enabled: true,
    guard: { enabled: true },
    allow_register: true,
    default_role_register: "free_user",
    roles: {
      // 仅着陆页数据
      anonymous: {
        is_default: true,
        implicit_allow: false,
        permissions: [
          {
            permission: "data.entity.read",
            effect: "allow",
            policies: [{
              condition: { entity: { $in: ["plans", "features"] } },
              effect: "allow",
            }],
          },
        ],
      },
      // 免费层:有限访问
      free_user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",
          {
            permission: "data.entity.create",
            effect: "allow",
            policies: [{
              condition: { entity: "projects" },
              effect: "allow",
            }],
          },
        ],
      },
      // 付费层:对自己数据的完全访问
      pro_user: {
        implicit_allow: false,
        permissions: [
          "data.entity.read",
          "data.entity.create",
          {
            permission: "data.entity.update",
            effect: "allow",
            policies: [{
              effect: "filter",
              filter: { owner_id: "@user.id" },
            }],
          },
          {
            permission: "data.entity.delete",
            effect: "allow",
            policies: [{
              effect: "filter",
              filter: { owner_id: "@user.id" },
            }],
          },
        ],
      },
      admin: { implicit_allow: true },
    },
  },
}

测试访问级别

测试公共访问

# 应该成功(匿名读取)
curl http://localhost:7654/api/data/posts

# 应该失败(匿名创建)
curl -X POST http://localhost:7654/api/data/posts \
  -H "Content-Type: application/json" \
  -d '{"title": "测试"}'
# 返回 403

测试认证访问

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

# 应该成功(认证创建)
curl -X POST http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "测试"}'

测试实体特定访问

# 公共实体 - 应该成功
curl http://localhost:7654/api/data/posts

# 私有实体 - 应该失败
curl http://localhost:7654/api/data/users
# 返回 403

测试过滤访问

# 匿名:仅看到已发布
curl http://localhost:7654/api/data/posts
# 返回:[{ status: "published" }, ...]

# 认证:看到所有包括草稿
curl http://localhost:7654/api/data/posts \
  -H "Authorization: Bearer $TOKEN"
# 返回:[{ status: "draft" }, { status: "published" }, ...]

前端集成

React:检查认证状态

import { useApp, useAuth } from "bknd/react";

function DataDisplay() {
  const { api } = useApp();
  const { user } = useAuth();
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    // 对匿名和认证都有效
    api.data.readMany("posts").then((res) => {
      if (res.ok) setPosts(res.data);
    });
  }, []);

  return (
    <div>
      {posts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          {/* 仅对认证用户显示编辑 */}
          {user && <button>编辑</button>}
        </article>
      ))}

      {/* 仅对认证显示创建 */}
      {user ? (
        <button>新帖子</button>
      ) : (
        <a href="/login">登录以创建帖子</a>
      )}
    </div>
  );
}

条件获取

function useProtectedData(entity: string) {
  const { api } = useApp();
  const { user, isLoading } = useAuth();
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (isLoading) return;

    api.data.readMany(entity).then((res) => {
      if (res.ok) {
        setData(res.data);
      } else {
        setError(res.error);
      }
    });
  }, [entity, user, isLoading]);

  return { data, error, isAuthenticated: !!user };
}

// 用法
function ProtectedPage() {
  const { data, error, isAuthenticated } = useProtectedData("projects");

  if (error?.status === 403 && !isAuthenticated) {
    return <LoginPrompt />;
  }

  return <DataList items={data} />;
}

常见陷阱

没有默认角色 = 没有公共访问

问题: 未认证请求出现“权限未授予”

修复: 添加默认角色:

{
  roles: {
    anonymous: {
      is_default: true,  // 公共访问所需!
      permissions: ["data.entity.read"],
    },
  },
}

守卫未启用

问题: 每个人都可以访问所有内容

修复: 启用守卫:

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

过滤器未应用

问题: 匿名用户看到所有记录,而不仅仅是过滤的

修复: 使用 effect: "filter" 而不是 effect: "allow"

// 错误 - 允许所有
{
  condition: { entity: "posts" },
  effect: "allow",
  filter: { published: true },  // 被忽略!
}

// 正确 - 应用过滤器
{
  condition: { entity: "posts" },
  effect: "filter",
  filter: { published: true },
}

敏感实体暴露

问题: 用户实体公开可读

修复: 使用实体条件:

{
  permissions: [
    {
      permission: "data.entity.read",
      effect: "allow",
      policies: [{
        // 仅允许特定实体
        condition: { entity: { $in: ["posts", "comments"] } },
        effect: "allow",
      }],
    },
  ],
}

认证头部未发送

问题: 用户已认证但仍获取公共数据

修复: 在获取中包括凭据:

// 浏览器使用cookies
fetch("/api/data/posts", { credentials: "include" });

// 基于令牌
fetch("/api/data/posts", {
  headers: { Authorization: `Bearer ${token}` },
});

访问矩阵参考

场景 匿名角色 用户角色 结果
公共读取 data.entity.read 所有CRUD 匿名:读取;用户:CRUD
仅私有 无/无默认 所有CRUD 匿名:403;用户:CRUD
实体特定 仅读取帖子 读取所有 匿名:帖子;用户:所有
过滤 过滤已发布 读取所有 匿名:已发布;用户:所有

该做与不该做

该做:

  • 在正好一个角色上设置 is_default: true 以启用公共访问
  • 使用实体条件来限制哪些实体是公共的
  • 使用过滤器策略来暴露仅适当的记录
  • 测试匿名和认证用户的访问
  • 保护敏感实体(如用户、设置)

不该做:

  • 忘记启用守卫(guard: { enabled: true }
  • 在匿名/默认角色上使用 implicit_allow: true
  • 公开用户数据而没有过滤器
  • 假设认证头部总是发送(检查前端代码)
  • 混淆 effect: "allow"effect: "filter"

相关技能

  • bknd-create-role - 定义角色用于授权
  • bknd-assign-permissions - 配置详细权限
  • bknd-row-level-security - 数据级访问控制
  • bknd-protect-endpoint - 保护自定义端点
  • bknd-setup-auth - 初始化认证系统