名称: 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 - 初始化认证系统