設定 Firebase RealTime Database 安全規則
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 的規則其實相當簡單,只是要熟悉其邏輯和運作方式,透過規則就能保護資料庫,也就能做出更彈性的應用了。
更多參考
意見回饋
如果有任何建議或問題,可傳送「意見表單」給我,謝謝~