为了保护用户的数据安全,云开发提供更灵活、可扩展、更细粒度的安全规则能力,开发者可以在云后台或者小程序开发工具上自定义安全规则,限制 C 端用户对数据库的访问权限。
注意:
与之前数据库的权限设置一样,安全规则对数据库的权限控制是文档级别的访问控制,对文档的属性访问控制目前还不支持。
示例:
安全规则仅限制 C 端用户的访问请求,对于开发者的请求(例如通过云函数的访问请求)不做任何限制。
配置
每个集合可以单独配置安全规则,通过
json
来描述安全规则配置,示例配置如下:{"read": "auth.uid==doc.uid","write": "doc.name=='zzz'"}
json
配置的 key
,是用户的操作类型
,value
是一个表达式。当表达式执行结果的值是 true
的时候,用户的操作允许执行;否则,用户的操作则不被允许。操作类型 | 说明 | 默认值 |
read | 读文档 | false |
write | 写文档,可以细分为 create、update、delete | false |
create | 新建文档 | 无 |
update | 更新文档 | 无 |
delete | 删除文档 | 无 |
说明
如果开发者没有详细指定写操作具体类型的规则时,默认读取
write
的规则。例如说开发者的安全规则配置如下:{"read": ".....","write": "....","create": "...."}
用户的新建文档操作会读取
create
的对应规则,而 update
操作会读取 write
的规则。表达式
表达式是伪代码的语句,配置的时候不能过长,单条表达式限制 1024 字符。
变量
全局变量
Request
字段名 | 类型 | 说明 |
data | Object | 请求数据,create,update 时存在,代表请求 data |
Auth
字段名 | 类型 | 说明 |
loginType | String | 登录方式,微信小程序过来的请求是没有此值的;web 登录下才有此值。 |
uid | String | 用户唯一 ID,微信小程序的请求没有此值 |
openid | String | 用户 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 集合中 id 为 user 的文档,key 为用户 uid 的属性值为 zzz |
函数
get
get(path: string): Document | null | undefined
根据参数获取指定 doc 内容,返回 undefined 或 null 表示文档不存在,否则为 Document 对象,表示查询得到的数据。
入参 | 类型 | 描述 |
path | String | 非空,格式为 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'] "}
2. 集合 A 包含 shopId、orderId 关联关系,集合 B 包含 owner,shopId 关联关系,对集合 A 查询,希望限制只查到当前用户有权限的 shop 的订单:
{"read:": "auth.openid in get(`database.B.${doc.shopId}`).owner"}
限制
get 参数中存在的变量 doc 需要在 query 条件中以 == 或 in 方式出现,若以 in 方式出现,只允许 in 唯一值, 即 doc.shopId in array, array.length == 1。
一个表达式最多可以有
3
个 get
函数,最多可以访问 10
个不同的文档。get 函数的嵌套深度最多为 2, 即
get(get(path))
。说明
在未使用变量的情况下,每个
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}
,或者 key
是 uid
并且 value
是 {uid}
,则在服务端自动将 value 替换为实际的值。集合查询
对于集合查询,查询条件必须是安全规则的子集,
doc
此时表示的是查询条件。这个子集是指规则上所有可能的子集,而不是实际数据的子集。
操作类型包括: read
、update
、delete
。示例
// 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 查询(需迁移改造)
操作类型包括:
read
、update
、delete
、create
。如果操作类型是
create
,会校验写入的数据是否符合安全规则的限制条件。如果操作类型是
read
、update
、delete
,同时安全规则包含了 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
command | description |
or | || 逻辑或 |
and | && 逻辑与 |
query command
command | description |
eq | == |
ne/neq | != |
gt | > |
gte | >= |
lt | < |
lte | <= |
in | in |
nin | !(in []) |
update command
command | description |
set | 覆盖写,{key: set(object)} |
remove | 删除字段, {key: remove()} |
计费
安全规则本身不收费,但是安全规则额外的数据访问会统计到计费中。
get 函数会产生额外的数据访问。
指定文档 ID 查询的所有写操作会产生一次数据访问。
权限设置对比
权限类型 | 设置示例 |
所有用户可读,仅创建者及管理员可写 |
|
仅创建者及管理员可读写 |
|
所有用户可读,仅管理员可写 |
|
仅管理员可读写 |
|
角色权限控制
您可以利用 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 "}
?