串接 Firebase 数据库 (2)
Firebase RealTime Database 的 Rules ( 安全规则 ) 可以让数据库具有多一层的安全保护,规则除了可以指定哪些数据可以读取或写入,也可以搭配 auth 根据用户进行规范,这篇教学将会介绍如何设定 RealTime Database Rules 安全规则。
快速导览:
安全规则基本写法
RealTime Database Rules 使用类似 JSON 格式的写法,透过 .read、.write 和 .validate 定义是否可以存取和写入。
| 类型 | 说明 |
|---|---|
| .read | 是否允许读取数据,布尔值判断为 ture 可以读取,false 不可读取。 |
| .write | 是否允许写入数据,布尔值判断为 ture 可以写入,false 不可写入。 |
| .validate | 判断值、属性、子属性...等的正确格式。 |
如果是刚建立的专案,会使用“禁止读取写入”的设定,最外层是 rules 属性,表示整份数据库的规则,内容就会包含读写或文件夹相关的规则,因为 .read 和 .write 都是 false,所以不论任何人都无法存取或写入。
如果将 .read 和 .write 设为 true,表示任何人都可以写入或读取数据。
此外,如果使用下面的写法,会让“有注册”的用户 ( 具备 uid ) 才能够读取、写入或删除数据。
{
"rules": {
".read": "auth.uid != null",
".write": "auth.uid != null"
}
}
定义规则顺序
Firebase 的规则采用“浅层判断至深层”的做法,只要是存取数据,规则的 true 与 false 一律由浅层 ( root 根节点 ) 开始判断,举例来说,使用规则模拟工具执行下列规则,a 节点的层级虽然写了 false 阻挡写入,但 root 节点却允许写入,就造成 a 节点仍然可以写入数据的情形 ( 浅层的规则会覆盖深层的规则 )。
{
"rules": {
".read": true,
".write":true,
"a": {
".read": false,
".write": false
}
}
}
如果不希望浅层的规则影响到浅层,可以“相关规则设定留空”( 不要指定 true 或 false ),如果不写读写的规则,会变成默认值 false,false 不会影响深层,如此一来就会自动采用深层的设定,举例来说,下方的规则指定了 a 节点不可读取和写入,b 节点则可以读取和写入,除了 b 节点之外,其他节点一率无法存取。
简单来说,起始为 0,遇到 false 就加 0,遇到 true 就加 1,如果最后结果大于 0 就表示可以写入或读取。
{
"rules": {
"a": {
".read": false,
".write": false
},
"b":{
".read": true,
".write": true
}
}
}
a 节点无法存取。
b 节点可以存取。
c 节点无法存取。
例如上面的例子,一开始如果是 true,不论哪层写了 false,最后得到的值都一定大于 0,表示一定可以写入或读取,反之如果一开始是 0,遇到 true 加 1,在 a 节点就可以写入或读取,而其他节点因为没有加 1 所以仍保持 0,就不能读取或写入了。
{
"rules": {
".read":false,
".write":false,
"a": {
".read": true,
".write": true
}
}
}
a 节点可以存取。
b 节点无法存取。
验证 validate
“浅层判断至深层”的原则只适用于单纯撰写 .read 和 .write 规则,如果是规则中包含了 .validate,只会影响自己的层级,不受“浅层判断至深层”的原则限制,例如下方的规则,在 root 节点会“验证新数据一定要包含 a 节点”,所以如果储存的数据包含 a 节点,就可以通过验证,但在 a 的层级却会被“验证新数据一定要是数字格式”所阻挡,导致数据一定得是 a 节点下的数字数据才能储存的状况。
{
"rules": {
".read":true,
".write":true,
".validate": "newData.hasChildren(['a'])",
"a": {
".read": false,
".write": false,
".validate": "newData.isNumber()"
}
}
}
如果数据是数字可以存取。
如果数据是文字无法存取。
默认变数 predefined variables
在安全规则里,除了单纯的撰写节点名称,还有以下几种默认的变数,由于每个变数都有其特定用法,在“节点”的命名上,必须要避开这些名称。
| 变数 | 说明 | .read | .write | .validate |
|---|---|---|---|---|
| now | 建立节点的时间 | - | - | O |
| root | 读取或写入数据之前,存在数据库的根节点 | O | O | - |
| newData | 写入数据之后会存在的数据,包含新的数据和原本的数据 | - | O | O |
| data | 读取或写入数据之前,存在数据库的数据 | O | O | O |
| $变数 | 通用变数符号,表示在某个节点内的所有节点 | O | O | O |
| auth | 身份验证使用的变数 | O | O | - |
now
now 表示“数据库写入的时间”,单位采用毫秒计算 ( 也就是从 1970 年 1 月 1 日到现在的毫秒数 ),下列的规则指定用户写入的数据,必须包含 time 的节点,节点内容为放置时的毫秒数,数值一定要在 now 之后。
{ "rules": { ".read":false, ".write":true, ".validate":"newData.child('time').val()<now" } }root
root 表示“根节点”,通常会搭配类似
.child的指令来辅助判断,下列的规则如果数据库内有 a 节点,且 a 节点内的 aa 节点的值为 ok,才能够读取数据,透过这个方法也可以达到保护数据库的效果。{ "rules": { ".read":"root.child('a').child('aa').val() == 'ok'", ".write":true } }如果节点 a/aa 的值是 ok,则可以存取。
如果节点 a/aa 的值不是 ok,则不可以存取。
newData
newData 表示“新数据”,新数据的判断只适用
.write和.vaildate,下方的规则,会限制新的数据一定得包含 a 和 b 节点才能写入。{ "rules": { ".read":true, ".write":"newData.hasChildren(['a','b'])" } }如果数据中有 a 和 b 两个节点,可以存取。
如果数据中只有 a 但没有 b 节点,则不可以存取。
data
data 表示“已经存在的数据”,可以用在
.read、.write和.vaildate,下方的规则,会判断目前数据库中 a 节点是否存在 lock 为 true 的数据,如果有,则无法写入,如果没有就可以写入。{ "rules": { ".read":true, "a":{ ".write":"data.child('lock').val() != true" } } }如果 a 里没有 lock 为 true 的节点,可以存取 。
如果 a 里有 lock 为 true 的节点,不可以存取 。
$变数
“$变数”的意思是“通用变数符号”,也就是不论该层节点如何命名,使用“$变数”就代表该层所有的节点名称,举例来说,下方的规则虽然是$a,但表示得并不是 a 节点,而是在 root 下一层的“所有节点”都能写入或读取数据。
{ "rules": { ".read": false, ".write": false, "$a": { ".read": true, ".write": true } } }auth
auth 用于“搭配 firebase 专案的注册账号功能”,透过判断注册的账号、uid...等相关信息,决定是否可以读取或写入数据,下方的规则,会判断账号的 uid 是否等于节点的名称或者 uid 是否存在,如果不等于则不会写入,如果 uid 相等则会写入,如果 uid 不存在则会新建一个名称为 uid 的节点。
因为第一层是
.write,若判断为 true 则后方不论.write规则为何都可以写入,所以第二层使用.validate就能避免“浅层判断至深层”( 请参考本篇文章上面的说明 )。{ "rules": { ".write":"auth.uid != null", "$user": { ".read": true, ".validate":"$user === auth.uid" } } }如果没有 uid ,在第一层就会被排除。
如果某个 uid 的节点名称不等于用户的 uid 时,这个节点不能写入数据。
如果数据库中已经有 uid 的节点,当节点名称等于用户的 uid 时,这个节点就可以写入数据。
保护已存在的数据
透过用户 auth 的规则,可以定义较为妥善的数据保护方法,但针对“匿名”的用户,大致上可以定义以下的保护条件:
- 不能读取整个数据库,只能读取某个节点 ( 表示知道名称才能读取 )。
- 不能删除数据,只能不断添加数据。
根据这两个条件,定义以下的规则,在 root 层禁止读取和写入,在之后的每个节点都可以读取,不过如果该节点有数据,则无法写入。
{
"rules": {
".read": false,
".write": false,
"$data": {
".read": true,
".write": "!data.exists()",
}
}
}
假设数据库中已经有了下方的这些数据:
test: {
a: 123
b: 456
c: 789
}
透过模拟可以发现,若要读取整份 data 会被拒绝。
单独读取 a 节点的数据是被允许的。
若要写入数据到 a 节点,因为 a 节点已经有数据,所以无法写入。
不过如果是全新的节点,就可以写入数据 ( 等同于使用 push 的功能 )
小结
Firebase RealTime Database 的规则其实相当简单,只是要熟悉其逻辑和运作方式,透过规则就能保护数据库,也就能做出更弹性的应用了。
更多参考
微信扫码关注
抖音扫码关注