Laf 云数据库访问策略实战指南:从客户端直连到细粒度权限控制 📅 发布时间:2026/9/16 16:46:13 👁 浏览次数: Laf 云数据库访问策略实战指南从客户端直连到细粒度权限控制【免费下载链接】lafLaf is a vibrant cloud development platform that provides essential tools like cloud functions, databases, and storage solutions. It enables developers to quickly unleash their creativity and bring innovative ideas to life with ease.项目地址: https://gitcode.com/GitHub_Trending/la/laf本文是 Laf 云数据库「访问策略」Database Policy的完整技术指南。Laf 允许前端通过 laf-client-sdk 携带一个dbProxyUrl直接读写云数据库而访问策略就是控制这种直连行为的安全阀门——它由若干集合访问规则组成以 JSON 形式声明每个集合允许客户端执行哪些读写操作。读完本文你将掌握访问策略的创建流程、read / add / update / remove / count权限的配置语义、condition / data / query / multi四大内置验证器的工作原理以及基于 JWT 用户身份uid的表达式级权限控制与字段级数据校验可直接用于生产项目的最小权限配置。为什么前端可以直连数据库在传统架构中前端操作数据库必须经过服务端接口中转。Laf 的思路是把数据库访问入口通过 SDK 暴露给客户端再用访问策略做安全控制。从前端视角看直连本质上是一个特殊的 HTTP 请求SDK 把请求地址拼接为baseUrl dbProxyUrl。这一点可以直接在源码中得到印证packages/client-sdk/src/request/request.ts 中构造请求 URL 的逻辑为const req_url this.options.baseUrl this.options.dbProxyUrlpackages/client-sdk/src/cloud.ts 将dbProxyUrl与baseUrl、getAccessToken一起作为Cloud实例的核心配置packages/client-sdk/src/types.ts 对dbProxyUrl的注释明确指出其含义是数据库访问代理的入口地址如/proxy/app、/proxy/admin。也就是说dbProxyUrl指向的/proxy/xxx就是服务端暴露的数据库代理入口。请求到达服务端后由packages/database-proxy这个独立的规则引擎接管它先解析请求对应的集合与操作类型read / add / update / remove / count等再逐一执行该集合配置的访问规则任何一条规则校验不通过请求即被拒绝。因此访问策略就是这套直连安全模型的配置面一个策略由多个集合的访问规则组成每个集合可分别配置读写操作的权限规则。开发者可以为应用创建多个策略供不同客户端使用最常见的划分是app用于用户端客户端App、小程序、H5 等admin用于应用的后台管理端。创建策略首先切换到 Laf 控制台的集合页面。如果还没有user集合先创建一个。然后按下面两步操作对应官方文档的操作截图第一步创建策略并命名第二步把策略绑定到集合上完成这两步后一个名为app的访问策略就被创建并绑定到了user集合上。策略默认生成的规则内容如下{ read: true, // 查询权限 count: true, // 计数权限 update: false, // 修改权限 remove: false, // 删除权限 add: false // 新增权限 }语义很简单值设为true代表允许false代表不允许。按这套默认规则前端可以对user集合执行查询和计数操作而新增、修改、删除均被拒绝。这里还有一个值得留意的关键点——入口地址。刚刚设置的策略名称为app那么对应的入口地址就是/proxy/app。这个入口地址就是上文dbProxyUrl要填写的值二者必须一一对应。规则简写形式的底层原理你可能好奇read: true这样一个布尔值是如何参与权限校验的。查看规则引擎的解析代码 packages/database-proxy/src/policy/policy.ts 可以发现一个归一化函数wrapRawPermissionRuleToArray权限规则为布尔值时会被转换为[{ condition: true }]或false权限规则为字符串时会被转换为[{ condition: 表达式字符串 }]对象会被包装成[对象]数组则原样保留。也就是说一切权限配置最终都会归一化为一组验证器配置而true / false只是condition验证器的语法糖。这也是为什么本文后续会看到read: uid query._id这样的字符串写法——它等价于{ condition: uid query._id }。另外引擎还兼容了旧版v1规则convertPermissionConfigpolicy.ts会自动把.read、.update、.add、.remove、.count这类带点前缀的权限名转换为 v2 的无点写法。仓库中的 rules-v1.json 与 rules-v2.json 就是新旧两种格式的对照样例。前端直连实战配置好策略后先在user集合中准备一些数据例如两条用户记录name: jack、name: rose方便观察查询效果接下来在前端项目以下以 Vue 项目演示其他框架同理中直连数据库。第一步安装 SDKnpm i laf-client-sdk第二步创建 Cloud 对象注意这里比普通云函数调用多填了一个参数dbProxyUrl它的值就是我们上面强调的入口地址/proxy/appimport { Cloud } from laf-client-sdk; const cloud new Cloud({ baseUrl: https:/APP_ID.laf.run, // APP_ID 在首页应用列表获取 getAccessToken: () , // 这里不需要授权先填空 dbProxyUrl: /proxy/app, // 这里就填写我们刚刚强调的入口地址 })第三步直接发起查询创建完 Cloud 对象之后尝试在前端直接查询user集合async function get() { const res await cloud.database().collection(user).get(); console.log(res); // { // data: [ // { // _id: 641d22292de2b789c963e5fd, // name: jack // }, // { // _id: 641e87d52c9ce609db4d6047, // name: rose // } // ], // ok: true // } }再试试计数async function get() { const res await cloud.database().collection(user).count(); console.log(res); // { // total: 2, // ok: true // } }可以看到简单的查询和计数已经无需编写云函数直接在前端即可实现。如果想体验增删改可以临时把对应策略的权限改为true——但这是一个非常危险的行为生产环境务必遵循下文的最小权限原则。从布尔值到表达式基于用户身份的权限控制true / false只能表达全员可读/全员不可读无法满足只能操作自己的数据这类需求。访问策略同样支持表达式以下为users用户集合的访问策略示例users { read: uid query._id }其中query是数据查询的条件对象uid query._id表示该策略只允许当前用户读取自己的用户数据。注意uid是 JWT Payload 中的字段代表当前登录用户的 ID。你可以在云函数中生成 JWT 令牌payload 中的任意字段都可在访问策略的表达式中使用。从实现层面看condition验证器packages/database-proxy/src/validators/condition/index.ts通过 Node.js 的vm模块在沙箱中执行这段表达式先合并injections即 JWT payload 字段如uid与params含query、data等请求参数作为全局上下文再求值表达式结果为真则校验通过否则返回错误。值得留意的是沙箱还注入了一个get函数表达式可以发起类似get(/users/id)的跨集合查询这为exists等高级约束提供了支撑。表达式语境中的真值遵循 JS 语义类似update: uid的写法含义是uid不为假值undefined、null、false等。内置验证器详解访问规则由一个或多个验证器组成。前面read: true使用的就是condition验证器它接收一个表达式来控制是否允许访问。上面的写法是简写形式完整写法如下{ read: { condition: true } }规则引擎在解析时会把同一权限下的多个验证器放入一条规则逐条执行任一验证器失败则这条规则不通过并继续尝试下一条规则见 policy.ts 的 validate 流程。当前引擎内置了以下验证器见 packages/database-proxy/src/validators/index.ts 的注册列表condition条件验证器。接受一个表达式返回真值即代表允许访问。表达式中默认可用的对象有JWT PayloadsJWT 令牌 payload 中的字段如示例中的uidquery数据查询条件data更新或新增数据操作时的数据对象。另外cond是它的别名。data数据验证器。接受一个对象对更新或新增数据操作时的数据对象中的各字段进行验证具体参考下文示例schema是它的别名用于承载集合的结构约束$schema。query查询条件对象验证器。接受一个对象对查询条件对象中的各字段进行验证。配置为数组时表示只允许数组内的字段作为查询条件配置为对象时则对每个字段做约束性检查query/index.ts。multi批量操作验证器。接受一个表达式作为配置。默认只有read操作允许批量操作源码中if (action ActionType.READ) allow_multi true其余操作如需开启批量必须显式指定此验证器multi/index.ts。此外引擎还注册了join与lookup两个聚合相关的验证器见 validators/index.ts且权限类型枚举中还包括watch与aggregatepolicy.ts说明策略体系对实时监听与聚合操作同样预留了权限位。字段级约束与 $schema 数据校验data验证器背后的字段约束系统定义在 packages/database-proxy/src/utils/constraint.ts 中支持的约束类型包括约束名说明配置示例required字段必填required: truein值必须属于给定集合in: [choice, fill]default字段缺省时自动填充默认值default: 0length字符串长度区间12 个整数元素length: [1, 64]number数值区间12 个整数元素缺省上限为 Infinitynumber: [0, 100]unique字段值在集合内唯一写入时查重unique: truematch正则匹配约束格式match: ^\\d{6,10}$exists值必须存在于指定集合的某字段中跨集合引用校验exists: /users/idnotExists值不得存在于指定集合的某字段中notExists: /users/idcondition/cond字段级条件表达式$value uid几个实现要点均可从 constraint.ts 印证字段约束表达式中$value别名$v代表当前字段的值因此author_id: $value uid表示提交的 author_id 必须等于当前登录用户 ID当提交数据中缺省某字段时若配置了default引擎会把默认值直接写入数据并跳过后续约束否则仅检查required更新update时引擎要求数据必须携带$set、$push等更新操作符并忽略required与default两类约束见 data/index.ts新增add时提交的数据不允许包含任何更新操作符见 data/index.ts无论data还是query验证器配置为数组时都表示只允许出现的字段白名单SecurityUtil.isAllowedFields。在 v2 规则中集合级还有一个特殊键$schema用于声明集合数据的结构约束它会自动附加到add与update的验证器配置上见 policy.ts。仓库中的 rules-v2.json 给出了一个完整的 v2 规则样例其中就包含$schema与$version: 2字段可对照阅读。实践建议初次接触访问规则可能会觉得它繁琐难掌握、容易写错或是对安全性不够自信。根据 Laf 在大量项目中的实践经验给出如下建议遵循最小权限原则只给客户端必要的访问权限只开放给客户端必要的集合。即便只开放read权限也可能节省 30%50% 的后端接口再配合一些基本的condition判断可有效减少 70%90% 的后端接口。事务性的数据操作、复杂的表单提交建议使用云函数实现云函数与客户端 SDK 接口一致简单、轻量、快捷无二次学习成本调试更容易、无部署负担云函数中访问数据库无需编写访问规则因为云函数是服务端执行的可信代码。访问规则虽然也能完成很多复杂的验证但除非你非常熟练地掌握了它否则建议用云函数来实现这部分逻辑。一句话总结这套分工访问策略管谁能直连、能做什么操作云函数管业务复杂度和事务完整性二者配合才能既高效又安全。访问规则示例以下提供几个由简到繁的示例帮助理解规则的组织方式。简单示例 1简单个人博客categories { read: true, update: uid, add: uid, remove: uid }, articles { read: true, update: uid, add: uid, remove: uid }提示其中类似update: uid的规则是uid不为假值的意思undefined | null | false等。简单示例 2多用户博客{ read: true, update: uid query.author_id, add: uid query.author_id, remove: uid query.author_id }所有用户可读全部文章只有作者本人uid等于文章author_id才能修改、新增、删除自己的文章。复杂示例 1数据验证{ read: true, add: { condition: uid query.author_id, data: { title: { length: [1, 64], required: true }, content: { length: [1, 4096] }, status: { boolean: [true, false] }, likes: { number: [0], default: 0 }, author_id: $value uid } }, remove: uid query.author_id, count: true }新增文章时必须通过条件验证作者身份与数据验证字段长度、类型、必填、默认值、字段值与登录用户一致。复杂示例 2更高级的数据验证场景介绍用户之间站内消息表访问规则{ read: uid query.receiver || uid query.sender, update: { condition: $uid query.receiver, data: { read: { in: [true] } } }, add: { condition: uid data.sender, data: { read: { in: [false] }, content: { length: [1, 20480], required: true }, receiver: { exists: /users/id }, read: { in: [true, false], default: false } } }, remove: false }该示例展示了站内信的典型约束只有收件人或发件人能读取消息只有收件人能更新且只能把read置为true表示已读发件人只能新增消息且receiver必须真实存在于users集合exists跨集合校验消息默认未读default: false。data 验证器示例categories { read: true, update: role admin, add: { condition: role admin, data: { password: { match: ^\\d{6,10}$ }, author_id: $value uid, type: { required: true, in: [choice, fill] }, title: { length: [4, 64], required: true, unique: true }, content: { length: [4, 20480] }, total: { number: [0, 100], default: 0, required: true } } } }该示例几乎用到了全部字段约束match校验密码为 610 位数字、$value uid强制写入者字段、in枚举取值、length长度区间、unique唯一性、number数值区间、default默认值与required必填。小结Laf 的访问策略机制本质上是把数据库代理与规则引擎两件事解耦packages/client-sdk负责把客户端的数据库操作请求发往/proxy/策略名入口packages/database-proxy则基于策略配置逐条执行验证器实现从整个集合放行/禁止到按 JWT 用户身份 查询条件 字段级约束的精细化权限控制。掌握好condition / data / query / multi四个验证器与字段约束的组合用法并坚持最小权限原则、把复杂逻辑交给云函数就能在享受前端直连开发效率的同时守住数据安全底线。更深入的工作原理可继续阅读 policy.ts、constraint.ts 以及仓库中的 ruler_v2_design.md 设计文档。【免费下载链接】lafLaf is a vibrant cloud development platform that provides essential tools like cloud functions, databases, and storage solutions. It enables developers to quickly unleash their creativity and bring innovative ideas to life with ease.项目地址: https://gitcode.com/GitHub_Trending/la/laf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考