跳到主要内容

安全规则

为了保护用户的数据安全,云开发提供更灵活、可扩展、更细粒度的安全规则能力,开发者可以在云后台或者小程序开发工具上自定义安全规则,限制 C 端用户对数据库的访问权限。

与之前数据库的权限设置一样,安全规则对数据库的权限控制是文档级别的访问控制,对文档的属性访问控制目前还不支持。

安全规则仅限制 C 端用户的访问请求,对于开发者的请求(如通过云函数的访问请求)不做任何限制。

配置#

每个集合可以单独配置安全规则,通过 json 来描述安全规则配置,示例配置如下:

{  "read": "auth.uid==doc.uid",  "write": "doc.name=='zzz'"}

json 配置的 key,是用户的操作类型value 是一个表达式。当表达式执行结果的值是 true 的时候,用户的操作允许执行;否则,用户的操作则不被允许。

操作类型说明默认值
read读文档false
write写文档,可以细分为 create、update、deletefalse
create新建文档
update更新文档
delete删除文档

说明#

如果开发者没有详细指定写操作具体类型的规则时,默认读取 write 的规则。例如说开发者的安全规则配置如下:

{  "read": ".....",  "write": "....",  "create": "...."}

用户的新建文档操作会读取 create 的对应规则,而 update 操作会读取 write 的规则。

表达式#

表达式是伪代码的语句,配置的时候不能过长,单条表达式限制 1024 字符。

变量#

全局变量#

变量名类型说明
requestRequest请求挂载的根对象
authAuth用户登录信息,字段说明参见下文
nownumber当前时间
docany文档数据或查询条件

Request#

字段名类型说明
dataobject请求数据,create,update 时存在,代表请求 data

Auth#

字段名类型说明
loginTypestring登录方式,微信小程序过来的请求是没有此值的;web 登录下才有此值。
uidstring用户唯一 id,微信小程序的请求没有此值
openidstring用户 openid,仅在微信登录方式下存在值

LoginType 枚举#

枚举值登录方式说明
WECHAT_PUBLIC微信公众号
WECHAT_OPEN微信开放平台
ANONYMOUS匿名登录
EMAIL邮箱登录
CUSTOM自定义登录

运算符#

运算符说明示例示例解释(集合查询)
==等于auth.uid == 'zzz'用户的 uid 为 zzz
!=不等于auth.uid != 'zzz'用户的 uid 不为 zzz
>大于doc.age>10查询条件的 age 属性大于 10
>=大于等于doc.age>=10查询条件的 age 属性大于等于 10
<小于doc.age>10查询条件的 age 属性小于 10
<=小于等于doc.age>=10查询条件的 age 属性小于等于 10
in存在于集合中auth.uid in ['zzz','aaa']用户的 uid 是['zzz','aaa']中的一个
!(xx in [])不存在于集合中,使用 in 的方式描述 !(a in [1,2,3])!(auth.uid in ['zzz','aaa'])用户的 uid 不是['zzz','aaa']中的任何一个
&&auth.uid == 'zzz' && doc.age>10用户的 uid 为 zzz 并且查询条件的 age 属性大于 10
||或者auth.uid == 'zzz' || doc.age>10用户的 uid 为 zzz 或者查询条件的 age 属性大于 10
.对象元素访问符auth.uid用户的 uid
[]数组访问符属性get('database.collection_a.user')[auth.uid] == 'zzz'collection_a 集合中 iduser 的文档,key 为用户 uid 的属性值为 zzz

函数#

目前仅支持 get 函数,唯一的参数必须为 database.集合名称.文档id。通过访问其它文档的数据来判断用户操作是否符合安全规则。

get#

get(path: string): Document | null | undefined

根据参数获取指定 doc 内容,返回 undefined 或 null 表示文档不存在,否则为 Document 对象,表示查询得到的数据。

入参类型描述
pathstring非空,格式为 database.集合名称.文档id 的字符串,值可以通过多种计算方式得到,例如使用字符串模版进行拼接:(database.${doc.collction}.${doc._id}

示例#

  1. 用户的权限是写在一个独立的文档,用一个数值表示用户的权限范围:
{  "read": "get('database.test.123')[auth.uid] in [1,2,3]",  "delete": "get('xxxx')[auth.uid] == 1 && doc.user in ['ersed','sfsdf'] "}
  1. 集合 A 包含 shopId、orderId 关联关系,集合 B 包含 owner,shopId 关联关系,对集合 A 查询,希望限制只查到当前用户有权限的 shop 的订单:
{  "read:": "auth.openid in get(`database.B.${doc.shopId}`).owner"}

限制#

  1. get 参数中存在的变量 doc 需要在 query 条件中以 == 或 in 方式出现,若以 in 方式出现,只允许 in 唯一值, 即 doc.shopId in array, array.length == 1
  2. 一个表达式最多可以有 3get 函数,最多可以访问 10 个不同的文档。
  3. get 函数的嵌套深度最多为 2, 即 get(get(path))

说明#

  1. 在未使用变量的情况下,每个 get 会产生一次读操作,在使用变量时,每个 get,每个变量值会产生一次读操作,例如:规则 get(`database.collection.${doc._id}`).test,在查询 _.or([{_id:1},{_id:2},{_id:3},{_id:4},{_id:5}])} 下会产生 5 次读取。系统会对同 doc,同 field 的读取进行缓存。

查询#

有效的查询#

在实际使用的过程中,查询主要分为两种,指定文档 id 的查询集合查询

指定文档 id 的查询通过 doc 条件指定单个文档 id。

集合查询可以是通过 where 条件的查询或者是聚合搜索 match 限制条件的查询,如果是聚合搜索,只匹配第一个 match 限制条件。

查询条件中,如果 key_openid 并且 value{openid},或者 keyuid 并且 value{uid},则在服务端自动将 value 替换为实际的值。

集合查询#

对于集合查询,查询条件必须是安全规则的子集,doc 此时表示的是查询条件。这个子集是指规则上所有可能的子集,而不是实际数据的子集。

操作类型包括 readupdatedelete

示例#

// colleciton_a 的安全规则的配置{    "read":"doc.age>10"}
// 符合安全规则let queryRes = db.collection('colleciton_a').where({    age:_.gt(10)}).get()
// 不符合安全规则let queryRes = db.collection('colleciton_a').where({    age:_.gt(8)}).get
// 符合安全规则let res = await db.collection('collection_a').aggregate().match({  age: _.gt(10)}).project({  age: 1}).end()
// 不符合安全规则let res = await db.collection('collection_a').aggregate().match({  age: _.gt(8)}).project({  age: 1}).end()

指定文档id查询(需迁移改造)#

操作类型包括 readupdatedeletecreate

如果操作类型是 create,会校验写入的数据是否符合安全规则的限制条件。

如果操作类型是 readupdatedelete,同时安全规则包含了 doc 的限制,则会先从 db 中读取一次文档数据,然后判断是否符合安全规则。

对于 update 操作,只会校验存在于数据库中已有的文档数据,不会对写入的数据进行校验;同时不保证这个操作的原子性。

由于安全规则要求查询条件是安全规则的子集(所有对 doc 的限制必须在查询条件中出现且查询条件限制范围是规则限制的子集, 例如:查询 a>20 是规则 a>10 的子集),同时摒弃了旧有权限配置的隐式默认行为,因此启动安全规则需要开发者注意以下升级/兼容处理。

doc 操作(doc.get, doc.set 等)是仅指定 _id 进行的操作,其查询条件仅包涵 {_id:“xxx”},因此大部分情况下并不会满足安全规则的子集要求(除非在 "read": true 下进行读操作或在 "write": true 的情况下进行写操作),因此需要转换为等价的、查询条件包含安全规则或是其子集的形式。

示例#

// colleciton_a 的安全规则的配置{    "read":"doc._openid == auth.openid"}
// 文档id='ccc'的数据是{  age:12}
// 不符合安全规则, 未满足子集需求let queryRes = db.collection('colleciton_a').doc('ccc').get()
// 符合安全规则,改写为 where 查询let queryRes = db.collection('colleciton_a').where({_id: "ccc", _openid: "{openid}"}).get()
注意

因基础权限控制下查询条件可以不传 _openid,而安全规则权限要求显示传入以保证查询条件符合安全规则,因此所有查询条件均需传入 openid。可使用模板 "{openid}" 或 "{uid}" 来代指当前登录用户的 openid 或 uid。

支持的数据库 command#

logic command#

commanddescription
or|| 逻辑或
and&& 逻辑与

query command#

commanddescription
eq==
ne/neq!=
gt>
gte>=
lt<
lte<=
inin
nin!(in [])

update command#

commanddescription
set覆盖写,{key: set(object)}
remove删除字段, {key: remove()}

计费#

安全规则本身不收费,但是安全规则额外的数据访问会统计到计费中。

  • get 函数会产生额外的数据访问;
  • 指定文档 id 查询的所有写操作会产生一次数据访问。

之前的权限设置对比#

所有用户可读,仅创建者及管理员可写#

{  "read": true,  "write": "doc._openid == auth.openid" // 登录方式为微信  "write": "doc._openid == auth.uid" // 登录方式为非微信}

仅创建者及管理员可读写#

{  "read": "doc._openid == auth.openid" //登录方式为微信  "read": "doc._openid == auth.uid" // 登录方式为非微信  "write": "doc._openid == auth.openid" //登录方式为微信  "write": "doc._openid == auth.uid" // 登录方式为非微信}

所有用户可读,仅管理员可写#

{  "read": true,  "write": false}

仅管理员可读写#

{  "read": false,  "write": false}

实现基于角色的权限控制#

您可以利用 CloudBase 的数据模型以及自定义的安全规则在您的应用中实现基于角色的访问权限控制。

假设您正在构建一款协作式撰文应用,用户可以按照以下安全要求在其中撰写“故事”和“评论”:

每个故事都有一名所有者;故事可共享给撰写者

撰写者除了拥有评论者所拥有的全部访问权限之外,还可以编辑故事内容。 所有者可以编辑故事的任意部分,并且可以控制其他用户的访问权限。 普通用户只能查看故事和评论,写自己的评论,不能编辑故事。

数据结构#

假设您的应用有一个 stories 集合,其中每个文档代表一个故事。

还有一个 comments 集合,其中每个文档代表一条针对该故事的评论。

一个 roles 集合,每个集合包含一个故事的用户的角色。

为了跟踪访问角色,需要添加一个 roles 字段,用来将用户 ID 映射至角色:

stories 集合#

每一个 story 文档数据数据如下:

{  "id": `storyid`,  "title": "A Great Story",  "content": "Once upon a time ..."}

roles 集合#

每一个 role 文档数据数据如下:

{  "id": `storyid`,  "roles": {    "alice": "owner",    "bob": "writer",    "david": "writer"    // ...  }}

comments 集合#

每一个 comment 文档数据如下,评论仅包含三个字段,留言者的用户 ID 、内容、storyid。

{  "id": `commentId`,  "storyid": `storyid`,  "user": "alice",  "content": "I think this is a great story!"}

规则#

既然数据库中记录有用户角色,那么便需要编写安全规则来进行角色验证。下列规则假定应用使用的是 TCB 身份验证,所以 auth.uid 变量为用户 ID。

roles 添加规则#

管理者可以更改角色,允许故事撰写者读取角色:

{  "write": "doc.roles[auth.uid] === 'owner' ",  "read": "doc.roles[auth.uid] in ['owner', 'writer']"}

stories 添加规则#

管理者和故事撰写者可以更改故事,其它人可以读取故事:

{  "read": true,  "write": "get(`database.roles.${doc.id}`).roles[auth.uid] in ['owner', 'writer'] "}

comments 规则#

允许所有人发表评论。 只有评论的拥有者,可以更新和删除评论:

{  "read": true,  "create": true,  "update": "doc.user == auth.uid",  "delete": "doc.user == auth.uid "}